mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-26 06:09:41 +00:00
feat: handle google login
This commit is contained in:
@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { Request } from 'express';
|
||||
import { OAuth2Client } from 'google-auth-library';
|
||||
import { CacheService } from '~/common/modules/cache/services';
|
||||
import { OtpScope, OtpType } from '~/common/modules/otp/enums';
|
||||
import { OtpService } from '~/common/modules/otp/services';
|
||||
@ -30,6 +31,11 @@ const SALT_ROUNDS = 10;
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly logger = new Logger(AuthService.name);
|
||||
private readonly googleWebClientId = this.configService.getOrThrow('GOOGLE_WEB_CLIENT_ID');
|
||||
private readonly googleAndroidClientId = this.configService.getOrThrow('GOOGLE_ANDROID_CLIENT_ID');
|
||||
private readonly googleIosClientId = this.configService.getOrThrow('GOOGLE_IOS_CLIENT_ID');
|
||||
private readonly client = new OAuth2Client();
|
||||
|
||||
constructor(
|
||||
private readonly otpService: OtpService,
|
||||
private readonly jwtService: JwtService,
|
||||
@ -119,6 +125,47 @@ export class AuthService {
|
||||
this.logger.log(`Passcode set successfully for user with id ${userId}`);
|
||||
}
|
||||
|
||||
async setPhoneNumber(userId: string, { phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
||||
const user = await this.userService.findUserOrThrow({ id: userId });
|
||||
|
||||
if (user.phoneNumber || user.countryCode) {
|
||||
this.logger.error(`Phone number already set for user with id ${userId}`);
|
||||
throw new BadRequestException('USERS.PHONE_NUMBER_ALREADY_SET');
|
||||
}
|
||||
|
||||
const existingUser = await this.userService.findUser({ phoneNumber, countryCode });
|
||||
|
||||
if (existingUser) {
|
||||
this.logger.error(`Phone number ${countryCode + phoneNumber} already taken`);
|
||||
throw new BadRequestException('USERS.PHONE_NUMBER_ALREADY_TAKEN');
|
||||
}
|
||||
|
||||
await this.userService.setPhoneNumber(userId, phoneNumber, countryCode);
|
||||
|
||||
return this.otpService.generateAndSendOtp({
|
||||
userId,
|
||||
recipient: countryCode + phoneNumber,
|
||||
scope: OtpScope.VERIFY_PHONE,
|
||||
otpType: OtpType.SMS,
|
||||
});
|
||||
}
|
||||
|
||||
async verifyPhoneNumber(userId: string, otp: string) {
|
||||
const isOtpValid = await this.otpService.verifyOtp({
|
||||
otpType: OtpType.SMS,
|
||||
scope: OtpScope.VERIFY_PHONE,
|
||||
userId,
|
||||
value: otp,
|
||||
});
|
||||
|
||||
if (!isOtpValid) {
|
||||
this.logger.error(`Invalid OTP for user with id ${userId}`);
|
||||
throw new BadRequestException('USERS.INVALID_OTP');
|
||||
}
|
||||
|
||||
return this.userService.verifyPhoneNumber(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);
|
||||
@ -201,32 +248,38 @@ export class AuthService {
|
||||
}
|
||||
|
||||
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;
|
||||
let user: User;
|
||||
let tokens: ILoginResponse;
|
||||
|
||||
if (!user) {
|
||||
this.logger.error(`User with email ${loginDto.email} not found`);
|
||||
throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS');
|
||||
if (loginDto.grantType === GrantType.GOOGLE) {
|
||||
this.logger.log(`Logging in user with email ${loginDto.email} using google`);
|
||||
[tokens, user] = await this.loginWithGoogle(loginDto);
|
||||
}
|
||||
|
||||
if (loginDto.grantType === GrantType.APPLE) {
|
||||
this.logger.log(`Logging in user with email ${loginDto.email} using apple`);
|
||||
throw new BadRequestException('AUTH.APPLE_LOGIN_NOT_IMPLEMENTED');
|
||||
}
|
||||
|
||||
if (loginDto.grantType === GrantType.PASSWORD) {
|
||||
this.logger.log(`Logging in user with email ${loginDto.email} using password`);
|
||||
tokens = await this.loginWithPassword(loginDto, user);
|
||||
} else {
|
||||
[tokens, user] = await this.loginWithPassword(loginDto);
|
||||
}
|
||||
|
||||
if (loginDto.grantType === GrantType.BIOMETRIC) {
|
||||
this.logger.log(`Logging in user with email ${loginDto.email} using biometric`);
|
||||
tokens = await this.loginWithBiometric(loginDto, user, deviceId);
|
||||
[tokens, user] = await this.loginWithBiometric(loginDto, deviceId);
|
||||
}
|
||||
|
||||
await this.deviceService.updateDevice(deviceId, {
|
||||
lastAccessOn: new Date(),
|
||||
fcmToken: loginDto.fcmToken,
|
||||
userId: user.id,
|
||||
userId: user!.id,
|
||||
});
|
||||
|
||||
this.logger.log(`User with email ${loginDto.email} logged in successfully`);
|
||||
|
||||
return [tokens, user];
|
||||
return [tokens!, user!];
|
||||
}
|
||||
|
||||
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
||||
@ -268,7 +321,9 @@ export class AuthService {
|
||||
return this.cacheService.set(accessToken, 'LOGOUT', expiryInTtl);
|
||||
}
|
||||
|
||||
private async loginWithPassword(loginDto: LoginRequestDto, user: User): Promise<ILoginResponse> {
|
||||
private async loginWithPassword(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> {
|
||||
const user = await this.userService.findUserOrThrow({ email: loginDto.email });
|
||||
|
||||
this.logger.log(`validating password for user with email ${loginDto.email}`);
|
||||
const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password);
|
||||
|
||||
@ -279,10 +334,12 @@ export class AuthService {
|
||||
|
||||
const tokens = await this.generateAuthToken(user);
|
||||
this.logger.log(`Password validated successfully for user with email ${loginDto.email}`);
|
||||
return tokens;
|
||||
return [tokens, user];
|
||||
}
|
||||
|
||||
private async loginWithBiometric(loginDto: LoginRequestDto, user: User, deviceId: string): Promise<ILoginResponse> {
|
||||
private async loginWithBiometric(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> {
|
||||
const user = await this.userService.findUserOrThrow({ email: loginDto.email });
|
||||
|
||||
this.logger.log(`validating biometric for user with email ${loginDto.email}`);
|
||||
const device = await this.deviceService.findUserDeviceById(deviceId, user.id);
|
||||
|
||||
@ -311,7 +368,36 @@ export class AuthService {
|
||||
|
||||
const tokens = await this.generateAuthToken(user);
|
||||
this.logger.log(`Biometric validated successfully for user with email ${loginDto.email}`);
|
||||
return tokens;
|
||||
return [tokens, user];
|
||||
}
|
||||
|
||||
private async loginWithGoogle(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> {
|
||||
try {
|
||||
const ticket = await this.client.verifyIdToken({
|
||||
idToken: loginDto.googleToken,
|
||||
audience: [this.googleWebClientId, this.googleAndroidClientId, this.googleIosClientId],
|
||||
});
|
||||
|
||||
const payload = ticket.getPayload();
|
||||
|
||||
const existingUser = await this.userService.findUser({ googleId: payload?.sub });
|
||||
|
||||
if (!existingUser) {
|
||||
this.logger.debug(`User with google id ${payload?.sub} not found, creating new user`);
|
||||
const user = await this.userService.createGoogleUser(payload!.sub, payload!.email!);
|
||||
|
||||
const tokens = await this.generateAuthToken(user);
|
||||
|
||||
return [tokens, user];
|
||||
}
|
||||
|
||||
const tokens = await this.generateAuthToken(existingUser);
|
||||
|
||||
return [tokens, existingUser];
|
||||
} catch (error) {
|
||||
this.logger.error(`Invalid google token`, error);
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
}
|
||||
|
||||
private async generateAuthToken(user: User) {
|
||||
|
Reference in New Issue
Block a user