mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-26 06:09:41 +00:00
feat: add apple login
This commit is contained in:
@ -3,7 +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 { ArrayContains } from 'typeorm';
|
||||
import { CacheService } from '~/common/modules/cache/services';
|
||||
import { OtpScope, OtpType } from '~/common/modules/otp/enums';
|
||||
import { OtpService } from '~/common/modules/otp/services';
|
||||
@ -22,19 +22,16 @@ import {
|
||||
setJuniorPasswordRequestDto,
|
||||
VerifyUserRequestDto,
|
||||
} from '../dtos/request';
|
||||
import { GrantType } from '../enums';
|
||||
import { GrantType, Roles } from '../enums';
|
||||
import { IJwtPayload, ILoginResponse } from '../interfaces';
|
||||
import { removePadding, verifySignature } from '../utils';
|
||||
import { Oauth2Service } from './oauth2.service';
|
||||
|
||||
const ONE_THOUSAND = 1000;
|
||||
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,
|
||||
@ -44,6 +41,7 @@ export class AuthService {
|
||||
private readonly deviceService: DeviceService,
|
||||
private readonly juniorTokenService: JuniorTokenService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly oauth2Service: Oauth2Service,
|
||||
) {}
|
||||
async sendRegisterOtp({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
||||
this.logger.log(`Sending OTP to ${countryCode + phoneNumber}`);
|
||||
@ -269,7 +267,7 @@ export class AuthService {
|
||||
|
||||
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');
|
||||
[tokens, user] = await this.loginWithApple(loginDto);
|
||||
}
|
||||
|
||||
if (loginDto.grantType === GrantType.PASSWORD) {
|
||||
@ -383,32 +381,64 @@ export class AuthService {
|
||||
}
|
||||
|
||||
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 { email, sub } = await this.oauth2Service.verifyGoogleToken(loginDto.googleToken);
|
||||
const [existingUser, isJunior] = await Promise.all([
|
||||
this.userService.findUser({ googleId: sub }),
|
||||
this.userService.findUser({ email, roles: ArrayContains([Roles.JUNIOR]) }),
|
||||
]);
|
||||
|
||||
const payload = ticket.getPayload();
|
||||
if (isJunior && email) {
|
||||
this.logger.error(`User with email ${email} is an already registered junior`);
|
||||
throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET');
|
||||
}
|
||||
|
||||
const existingUser = await this.userService.findUser({ googleId: payload?.sub });
|
||||
if (!existingUser) {
|
||||
this.logger.debug(`User with google id ${sub} not found, creating new user`);
|
||||
const user = await this.userService.createGoogleUser(sub, email);
|
||||
|
||||
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);
|
||||
|
||||
const tokens = await this.generateAuthToken(user);
|
||||
return [tokens, user];
|
||||
}
|
||||
|
||||
return [tokens, user];
|
||||
const tokens = await this.generateAuthToken(existingUser);
|
||||
|
||||
return [tokens, existingUser];
|
||||
}
|
||||
|
||||
private async loginWithApple(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> {
|
||||
const { sub, email } = await this.oauth2Service.verifyAppleToken(loginDto.appleToken);
|
||||
|
||||
const [existingUser, isJunior] = await Promise.all([
|
||||
this.userService.findUser({ appleId: sub }),
|
||||
this.userService.findUser({ email, roles: ArrayContains([Roles.JUNIOR]) }),
|
||||
]);
|
||||
|
||||
if (isJunior && email) {
|
||||
this.logger.error(`User with email ${email} is an already registered junior`);
|
||||
throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET');
|
||||
}
|
||||
|
||||
if (!existingUser) {
|
||||
// Apple only provides email if user authorized zod for the first time
|
||||
if (!email) {
|
||||
this.logger.error(`User authorized zod before but his email is not stored in the database`);
|
||||
throw new BadRequestException('AUTH.APPLE_RE-CONSENT_REQUIRED');
|
||||
}
|
||||
|
||||
const tokens = await this.generateAuthToken(existingUser);
|
||||
this.logger.debug(`User with apple id ${sub} not found, creating new user`);
|
||||
const user = await this.userService.createAppleUser(sub, email);
|
||||
|
||||
return [tokens, existingUser];
|
||||
} catch (error) {
|
||||
this.logger.error(`Invalid google token`, error);
|
||||
throw new UnauthorizedException();
|
||||
const tokens = await this.generateAuthToken(user);
|
||||
|
||||
return [tokens, user];
|
||||
}
|
||||
|
||||
const tokens = await this.generateAuthToken(existingUser);
|
||||
|
||||
this.logger.log(`User with apple id ${sub} logged in successfully`);
|
||||
|
||||
return [tokens, existingUser];
|
||||
}
|
||||
|
||||
private async generateAuthToken(user: User) {
|
||||
|
Reference in New Issue
Block a user