Merge pull request #88 from SyncrowIOT/feature/google-login

added google login
This commit is contained in:
yousef-khriasat-uba
2024-10-07 09:13:00 +03:00
committed by GitHub
7 changed files with 240 additions and 26 deletions

View File

@ -90,3 +90,6 @@ FIREBASE_DATABASE_URL=
OTP_LIMITER=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

View File

@ -1,4 +1,8 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import {
BadRequestException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as argon2 from 'argon2';
import { HelperHashService } from '../../helper/services';
@ -6,16 +10,21 @@ import { UserRepository } from '../../../../common/src/modules/user/repositories
import { UserSessionRepository } from '../../../../common/src/modules/session/repositories/session.repository';
import { UserSessionEntity } from '../../../../common/src/modules/session/entities';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
import { OAuth2Client } from 'google-auth-library';
@Injectable()
export class AuthService {
private client: OAuth2Client;
constructor(
private jwtService: JwtService,
private readonly userRepository: UserRepository,
private readonly sessionRepository: UserSessionRepository,
private readonly helperHashService: HelperHashService,
private readonly configService: ConfigService,
) {}
) {
this.client = new OAuth2Client(this.configService.get('GOOGLE_CLIENT_ID'));
}
async validateUser(
email: string,
@ -80,8 +89,17 @@ export class AuthService {
type: user.type,
sessionId: user.sessionId,
roles: user?.roles,
googleCode: user.googleCode,
};
if (payload.googleCode) {
const profile = await this.getProfile(payload.googleCode);
user = await this.userRepository.findOne({
where: { email: profile.email },
});
if (!user) {
return { profile };
}
}
const tokens = await this.getTokens(payload);
await this.updateRefreshToken(user.uuid, tokens.refreshToken);
return tokens;
@ -100,4 +118,19 @@ export class AuthService {
hashData(data: string) {
return argon2.hash(data);
}
async getProfile(googleCode: string) {
try {
const ticket = await this.client.verifyIdToken({
idToken: googleCode,
audience: this.configService.get('GOOGLE_CLIENT_ID'),
});
const payload = ticket.getPayload();
return {
...payload,
};
} catch (error) {
throw new UnauthorizedException('Google login failed');
}
}
}

164
package-lock.json generated
View File

@ -20,13 +20,14 @@
"@nestjs/websockets": "^10.3.8",
"@tuya/tuya-connector-nodejs": "^2.1.2",
"argon2": "^0.40.1",
"axios": "^1.6.7",
"axios": "^1.7.7",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"crypto-js": "^4.2.0",
"express-rate-limit": "^7.1.5",
"firebase": "^10.12.5",
"google-auth-library": "^9.14.1",
"helmet": "^7.1.0",
"ioredis": "^5.3.2",
"morgan": "^1.10.0",
@ -3564,6 +3565,18 @@
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
@ -3784,11 +3797,12 @@
"integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g=="
},
"node_modules/axios": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.4",
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@ -3962,6 +3976,15 @@
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
},
"node_modules/bignumber.js": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
"integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@ -5887,15 +5910,16 @@
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@ -6056,6 +6080,35 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gaxios": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
"integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
"license": "Apache-2.0",
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^7.0.1",
"is-stream": "^2.0.0",
"node-fetch": "^2.6.9",
"uuid": "^9.0.1"
},
"engines": {
"node": ">=14"
}
},
"node_modules/gcp-metadata": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
"integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
"license": "Apache-2.0",
"dependencies": {
"gaxios": "^6.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -6194,6 +6247,44 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/google-auth-library": {
"version": "9.14.1",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz",
"integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==",
"license": "Apache-2.0",
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"gaxios": "^6.1.1",
"gcp-metadata": "^6.1.0",
"gtoken": "^7.0.0",
"jws": "^4.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/google-auth-library/node_modules/jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/google-auth-library/node_modules/jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.0",
"safe-buffer": "^5.0.1"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@ -6217,6 +6308,40 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
"node_modules/gtoken": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
"integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
"license": "MIT",
"dependencies": {
"gaxios": "^6.0.0",
"jws": "^4.0.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/gtoken/node_modules/jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/gtoken/node_modules/jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.0",
"safe-buffer": "^5.0.1"
}
},
"node_modules/har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@ -6384,6 +6509,19 @@
"npm": ">=1.3.7"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
"integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.0.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -6689,7 +6827,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"engines": {
"node": ">=8"
},
@ -7533,6 +7670,15 @@
"node": ">=4"
}
},
"node_modules/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"license": "MIT",
"dependencies": {
"bignumber.js": "^9.0.0"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",

View File

@ -31,13 +31,14 @@
"@nestjs/websockets": "^10.3.8",
"@tuya/tuya-connector-nodejs": "^2.1.2",
"argon2": "^0.40.1",
"axios": "^1.6.7",
"axios": "^1.7.7",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"crypto-js": "^4.2.0",
"express-rate-limit": "^7.1.5",
"firebase": "^10.12.5",
"google-auth-library": "^9.14.1",
"helmet": "^7.1.0",
"ioredis": "^5.3.2",
"morgan": "^1.10.0",

View File

@ -41,5 +41,5 @@ export class UserSignUpDto {
@IsString()
@IsOptional()
public regionUuid: string;
public regionUuid?: string;
}

View File

@ -1,19 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { IsEmail, IsOptional, IsString } from 'class-validator';
export class UserLoginDto {
@ApiProperty()
@IsEmail()
@IsNotEmpty()
email: string;
@IsOptional()
email?: string;
@ApiProperty()
@IsString()
@IsOptional()
password: string;
password?: string;
@ApiProperty()
@IsString()
@IsOptional()
regionUuid?: string;
@IsOptional()
@IsString()
googleCode?: string;
}

View File

@ -19,6 +19,7 @@ import * as argon2 from 'argon2';
import { differenceInSeconds } from '@app/common/helper/differenceInSeconds';
import { LessThan, MoreThan } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { UUID } from 'typeorm/driver/mongodb/bson.typings';
@Injectable()
export class UserAuthService {
@ -89,13 +90,38 @@ export class UserAuthService {
async userLogin(data: UserLoginDto) {
try {
const user = await this.authService.validateUser(
data.email,
data.password,
data.regionUuid,
);
if (!user) {
throw new UnauthorizedException('Invalid login credentials.');
let user;
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'],
});
}
data.email = googleUserData['email'];
data.password = googleUserData['password'];
}
if (!data.googleCode) {
user = await this.authService.validateUser(
data.email,
data.password,
data.regionUuid,
);
}
const session = await Promise.all([
await this.sessionRepository.update(
@ -110,7 +136,7 @@ export class UserAuthService {
isLoggedOut: false,
}),
]);
return await this.authService.login({
const res = await this.authService.login({
email: user.email,
userId: user.uuid,
uuid: user.uuid,
@ -119,6 +145,7 @@ export class UserAuthService {
}),
sessionId: session[1].uuid,
});
return res;
} catch (error) {
throw new BadRequestException('Invalid credentials');
}