From cc3a7d6ddada9b57f02418c7898a67fa1f24764e Mon Sep 17 00:00:00 2001 From: Himanshu Kansal Date: Tue, 9 May 2023 16:20:12 +0530 Subject: [PATCH] Google SSO | Flow Revamp (#325) * TP-27854 | SSO revamp * TP-27854 | Config changes * TP-27854 | Handle signout * TP-27854 | Contract update * TP-27854 | Fix corner cases * TP-27854 | Corner cases fix * TP-27854 | fix --- android/app/build.gradle | 6 +- android/app/google-services.json | 72 +++++++++---------- android/build.gradle | 1 + config/dev/config.js | 4 +- config/prod/config.js | 2 + config/qa/config.js | 4 +- config/qa/google-services.json | 72 +++++++++---------- package.json | 4 +- src/action/authActions.ts | 116 +++++++++++------------------- src/components/utlis/apiHelper.ts | 4 +- src/constants/config.js | 4 +- src/screens/auth/AuthRouter.tsx | 15 ---- src/screens/login/index.tsx | 36 ++++++++-- 13 files changed, 162 insertions(+), 178 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 265a7b9f..ba4ea6d6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,4 @@ apply plugin: "com.android.application" -apply plugin: 'com.google.gms.google-services' apply plugin: "com.google.firebase.crashlytics" import com.android.build.OutputFile import org.apache.tools.ant.taskdefs.condition.Os @@ -131,8 +130,8 @@ def reactNativeArchitectures() { return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] } -def VERSION_CODE = 57 -def VERSION_NAME = "2.1.16" +def VERSION_CODE = 58 +def VERSION_NAME = "2.1.17" android { ndkVersion rootProject.ext.ndkVersion @@ -325,3 +324,4 @@ def isNewArchitectureEnabled() { // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" } +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/android/app/google-services.json b/android/app/google-services.json index 9d2a90ba..f48fcb67 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1,39 +1,39 @@ { - "project_info": { - "project_number": "60755663443", - "project_id": "address-verification-app", - "storage_bucket": "address-verification-app.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:60755663443:android:988149d3da3c00d38584a6", - "android_client_info": { - "package_name": "com.avapp" - } - }, - "oauth_client": [ - { - "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyA70_d2M2ke-Mu0OHGZ6iZilBbD6A-_z0c" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com", - "client_type": 3 - } - ] - } + "project_info": { + "project_number": "60755663443", + "project_id": "address-verification-app", + "storage_bucket": "address-verification-app.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:60755663443:android:988149d3da3c00d38584a6", + "android_client_info": { + "package_name": "com.avapp" + } + }, + "oauth_client": [ + { + "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyA70_d2M2ke-Mu0OHGZ6iZilBbD6A-_z0c" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com", + "client_type": 3 + } + ] } } - ], - "configuration_version": "1" - } \ No newline at end of file + } + ], + "configuration_version": "1" +} diff --git a/android/build.gradle b/android/build.gradle index 47593d68..cd97c39b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,6 +7,7 @@ buildscript { minSdkVersion = 21 compileSdkVersion = 31 targetSdkVersion = 31 + googlePlayServicesAuthVersion = "19.2.0" if (System.properties['os.arch'] == "aarch64") { // For M1 Users we need to use the NDK 24 which added support for aarch64 diff --git a/config/dev/config.js b/config/dev/config.js index 2a59e256..0b20b602 100644 --- a/config/dev/config.js +++ b/config/dev/config.js @@ -3,6 +3,8 @@ export const SENTRY_DSN = 'https://acef93c884c1424cacc4ec899562e203@qa-longhorn-portal.np.navi-tech.in/glitchtip-events/173'; export const JANUS_SERVICE_URL = 'https://dev-longhorn-portal.np.navi-tech.in/api/events/json'; export const ENV = 'dev'; -export const IS_SSO_ENABLED = false; +export const IS_SSO_ENABLED = true; export const APM_APP_NAME = 'cosmos-app'; export const APM_BASE_URL = 'https://dev-longhorn-portal.np.navi-tech.in/apm-events'; +export const GOOGLE_SSO_CLIENT_ID = + '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; diff --git a/config/prod/config.js b/config/prod/config.js index f2332a58..8ef9052b 100644 --- a/config/prod/config.js +++ b/config/prod/config.js @@ -10,3 +10,5 @@ export const APM_APP_NAME = 'cosmos-app'; export const APM_BASE_URL = 'https://longhorn.navi.com/apm-events'; export const IS_DATA_SYNC_REQUIRED = true; export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr +export const GOOGLE_SSO_CLIENT_ID = + '136591056725-ev8db4hrlud2m23n0o03or3cmmp3a3cq.apps.googleusercontent.com'; diff --git a/config/qa/config.js b/config/qa/config.js index 0b88831d..6fa05aeb 100644 --- a/config/qa/config.js +++ b/config/qa/config.js @@ -5,8 +5,10 @@ export const SENTRY_DSN = 'https://acef93c884c1424cacc4ec899562e203@qa-longhorn-portal.np.navi-tech.in/glitchtip-events/173'; export const JANUS_SERVICE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/api/events/json'; export const ENV = 'qa'; -export const IS_SSO_ENABLED = false; +export const IS_SSO_ENABLED = true; export const APM_APP_NAME = 'cosmos-app'; export const APM_BASE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/apm-events'; export const IS_DATA_SYNC_REQUIRED = true; export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr +export const GOOGLE_SSO_CLIENT_ID = + '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; diff --git a/config/qa/google-services.json b/config/qa/google-services.json index db37cb59..f48fcb67 100644 --- a/config/qa/google-services.json +++ b/config/qa/google-services.json @@ -1,39 +1,39 @@ { - "project_info": { - "project_number": "1073868858509", - "project_id": "qa-address-verification-app", - "storage_bucket": "qa-address-verification-app.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:1073868858509:android:1cc386054733f020d7eec4", - "android_client_info": { - "package_name": "com.avapp" - } - }, - "oauth_client": [ - { - "client_id": "1073868858509-eumumh6fhdkcc1spm465itqnepp7uc5c.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyAArgdF-UGxr4kaLnzXjVhgLQTcbyBWeT0" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "1073868858509-eumumh6fhdkcc1spm465itqnepp7uc5c.apps.googleusercontent.com", - "client_type": 3 - } - ] - } + "project_info": { + "project_number": "60755663443", + "project_id": "address-verification-app", + "storage_bucket": "address-verification-app.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:60755663443:android:988149d3da3c00d38584a6", + "android_client_info": { + "package_name": "com.avapp" + } + }, + "oauth_client": [ + { + "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyA70_d2M2ke-Mu0OHGZ6iZilBbD6A-_z0c" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com", + "client_type": 3 + } + ] } } - ], - "configuration_version": "1" - } \ No newline at end of file + } + ], + "configuration_version": "1" +} diff --git a/package.json b/package.json index 0e9bf623..a3e6b697 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "release:qa": "yarn move:qa && react-native run-android --variant=release && cd android && ./gradlew assembleRelease", "release:prod": "yarn move:prod && react-native run-android --variant=release && cd android && ./gradlew assembleRelease", "move:dev": "cp -f ./config/dev/config.js ./src/constants && cp -f ./config/dev/google-services.json ./android/app", - "move:qa": "cp -f ./config/qa/config.js ./src/constants && cp -f ./config/dev/google-services.json ./android/app", + "move:qa": "cp -f ./config/qa/config.js ./src/constants && cp -f ./config/qa/google-services.json ./android/app", "move:prod": "cp -f ./config/prod/config.js ./src/constants && cp -f ./config/prod/google-services.json ./android/app", "debug": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res", "prepare": "husky install" @@ -35,6 +35,7 @@ "@react-native-firebase/database": "16.4.6", "@react-native-firebase/firestore": "16.5.0", "@react-native-firebase/messaging": "17.4.0", + "@react-native-google-signin/google-signin": "9.0.2", "@react-navigation/bottom-tabs": "6.5.5", "@react-navigation/native": "6.1.4", "@react-navigation/native-stack": "6.9.4", @@ -45,7 +46,6 @@ "appcenter-analytics": "^4.4.5", "appcenter-crashes": "^4.4.5", "axios": "1.2.1", - "crypto-js": "^4.1.1", "fuzzysort": "2.0.4", "lottie-react-native": "5.1.4", "react": "18.1.0", diff --git a/src/action/authActions.ts b/src/action/authActions.ts index aef538c3..912cda4d 100644 --- a/src/action/authActions.ts +++ b/src/action/authActions.ts @@ -1,6 +1,5 @@ import { ToastMessages } from './../screens/allCases/constants'; import 'react-native-get-random-values'; -import crypto from 'crypto-js'; import { IUser, setAuthData } from '../reducer/userSlice'; import axiosInstance, { ApiKeys, API_STATUS_CODE, getApiUrl } from '../components/utlis/apiHelper'; import { @@ -21,8 +20,9 @@ import { toast } from '../../RN-UI-LIB/src/components/toast'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { clearAllAsyncStorage, setAsyncStorageItem } from '../components/utlis/commonFunctions'; import { logError } from '../components/utlis/errorUtils'; -import { Linking } from 'react-native'; import auth from '@react-native-firebase/auth'; +import { GenericType } from '../common/GenericTypes'; +import { GoogleSignin } from '@react-native-google-signin/google-signin'; export interface GenerateOTPPayload { phoneNumber: string; @@ -33,10 +33,6 @@ export interface VerifyOTPPayload { otpToken: string; } -export interface SignInGooglePayload { - codeChallenge: string; -} - export const generateOTP = ({ phoneNumber }: GenerateOTPPayload, isResendOTP?: boolean) => (dispatch: Dispatch) => { @@ -71,80 +67,46 @@ export const generateOTP = .finally(() => dispatch(setFormLoading(false))); }; -export const signInGoogle = () => async (dispatch: Dispatch) => { +export const verifyGoogleSignIn = (idToken: string) => async (dispatch: AppDispatch) => { + const url = getApiUrl(ApiKeys.VERIFY_GOOGLE_SIGN_IN); + const fcmToken = await AsyncStorage.getItem('fcmtoken'); + try { - const codeVerifier = crypto.lib.WordArray.random(64).toString(); - const codeChallenge = crypto.enc.Base64url.stringify( - crypto.enc.Utf8.parse(crypto.SHA256(codeVerifier)) - ).toString(); - await setAsyncStorageItem('codeVerifier', codeVerifier); - const state = crypto.lib.WordArray.random(16).toString(); - const url = getApiUrl(ApiKeys.SIGN_IN_GOOGLE); - const response = await axiosInstance.get(url, { - params: { - codeChallenge, - state, - }, - headers: { donotHandleError: false }, + const response = await axiosInstance.post(url, { + idToken, + fcmToken, + deviceId: GLOBAL.DEVICE_ID, + deviceType: GLOBAL.DEVICE_TYPE, }); - if (response?.data?.signInUrl) { - await Linking.openURL(response.data.signInUrl); + if (response?.data?.sessionDetails) { + const { sessionDetails, user } = response.data; + dispatch( + setAuthData({ + sessionDetails, + user, + isLoggedIn: true, + }) + ); + dispatch(setVerifyOTPSuccess('Login Successfully!')); + dispatch(resetLoginForm()); } - } catch (error) { + } catch (error: GenericType) { + await handleGoogleLogout(); logError(error as Error); - toast({ - text1: 'Error in google sign in', - type: 'error', - }); + if (error?.response?.status === API_STATUS_CODE.NOT_FOUND) { + toast({ + text1: ToastMessages.GENERIC_ERROR_TOAST, + type: 'error', + }); + } else { + toast({ + text1: error?.response?.data?.message ?? ToastMessages.SSO_SERVER_SIGN_IN_ERROR, + type: 'error', + }); + } } }; -export const verifyGoogleSignIn = - (code: string, state: string, deviceId: string) => async (dispatch: AppDispatch) => { - const url = getApiUrl(ApiKeys.VERIFY_GOOGLE_SIGN_IN); - try { - const codeVerify = await AsyncStorage.getItem('codeVerifier'); - const fcmToken = await AsyncStorage.getItem('fcmtoken'); - if (!codeVerify) { - return; - } - const parsedCodeVerify = JSON.parse(codeVerify); - const response = await axiosInstance.post(url, { - code, - state, - codeVerifier: parsedCodeVerify, - deviceId: deviceId, - deviceType: GLOBAL.DEVICE_TYPE, - fcmToken, - }); - if (response?.data?.sessionDetails) { - const { sessionDetails, user } = response.data; - dispatch( - setAuthData({ - sessionDetails, - user, - isLoggedIn: true, - }) - ); - dispatch(setVerifyOTPSuccess('OTP verified')); - dispatch(resetLoginForm()); - } - } catch (error) { - logError(error as Error); - if (error?.response?.status === API_STATUS_CODE.NOT_FOUND) { - toast({ - text1: ToastMessages.GENERIC_ERROR_TOAST, - type: 'error', - }); - } else { - toast({ - text1: error?.response?.data?.message ?? ToastMessages.SSO_SERVER_SIGN_IN_ERROR, - type: 'error', - }); - } - } - }; - export const verifyOTP = ({ otp, otpToken }: VerifyOTPPayload) => async (dispatch: AppDispatch) => { @@ -189,9 +151,17 @@ export const logout = () => (dispatch: AppDispatch) => { }); }; +export const handleGoogleLogout = async () => { + const currentUser = await GoogleSignin.getCurrentUser(); + if (currentUser) { + await GoogleSignin.signOut(); + } +}; + export const handleLogout = () => async (dispatch: AppDispatch) => { try { await auth().signOut(); + await handleGoogleLogout(); await clearAllAsyncStorage(); setGlobalUserData({ token: '', agentId: '', deviceId: '' }); dispatch( diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index 4a8c37d3..c1abd169 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -31,7 +31,6 @@ export enum ApiKeys { NOTIFICATION_ACTION = 'NOTIFICATION_ACTION', NOTIFICATION_DELIVERED = 'NOTIFICATION_DELIVERED', SEND_LOCATION = 'SEND_LOCATION', - SIGN_IN_GOOGLE = 'SIGN_IN_GOOGLE', VERIFY_GOOGLE_SIGN_IN = 'VERIFY_GOOGLE_SIGN_IN', SYNC_TIME = 'SYNC_TIME', IS_DATA_SYNC_REQUIRED = 'IS_DATA_SYNC_REQUIRED', @@ -59,8 +58,7 @@ API_URLS[ApiKeys.NOTIFICATIONS] = '/notification/fetch'; API_URLS[ApiKeys.NOTIFICATION_ACTION] = '/notification/action'; API_URLS[ApiKeys.NOTIFICATION_DELIVERED] = '/notification/delivered'; API_URLS[ApiKeys.SEND_LOCATION] = '/geolocations/agents'; -API_URLS[ApiKeys.SIGN_IN_GOOGLE] = '/auth/google/sign-in/url'; -API_URLS[ApiKeys.VERIFY_GOOGLE_SIGN_IN] = '/auth/session/exchange'; +API_URLS[ApiKeys.VERIFY_GOOGLE_SIGN_IN] = '/auth/session/internal/exchange'; API_URLS[ApiKeys.SYNC_TIME] = '/sync/server-timestamp'; API_URLS[ApiKeys.IS_DATA_SYNC_REQUIRED] = '/sync-data/is-sync-required'; API_URLS[ApiKeys.GET_PRE_SIGNED_URL_DATA_SYNC] = '/sync-data/get-pre-signed-url'; diff --git a/src/constants/config.js b/src/constants/config.js index 0b88831d..6fa05aeb 100644 --- a/src/constants/config.js +++ b/src/constants/config.js @@ -5,8 +5,10 @@ export const SENTRY_DSN = 'https://acef93c884c1424cacc4ec899562e203@qa-longhorn-portal.np.navi-tech.in/glitchtip-events/173'; export const JANUS_SERVICE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/api/events/json'; export const ENV = 'qa'; -export const IS_SSO_ENABLED = false; +export const IS_SSO_ENABLED = true; export const APM_APP_NAME = 'cosmos-app'; export const APM_BASE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/apm-events'; export const IS_DATA_SYNC_REQUIRED = true; export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr +export const GOOGLE_SSO_CLIENT_ID = + '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; diff --git a/src/screens/auth/AuthRouter.tsx b/src/screens/auth/AuthRouter.tsx index 75e55473..946b9f88 100644 --- a/src/screens/auth/AuthRouter.tsx +++ b/src/screens/auth/AuthRouter.tsx @@ -1,8 +1,6 @@ import { Linking, StyleSheet } from 'react-native'; import React, { useEffect } from 'react'; -import { getParamsObject } from '../../components/utlis/commonFunctions'; import { useAppDispatch } from '../../hooks'; -import { verifyGoogleSignIn } from '../../action/authActions'; import { useSelector } from 'react-redux'; import { RootState } from '../../store/store'; import { getUniqueId, isTablet } from 'react-native-device-info'; @@ -23,19 +21,6 @@ const AuthRouter = () => { const { isLoggedIn, deviceId, sessionDetails } = user; useNativeButtons(); - // Google signin url handler - const handleUrl = (url: string) => { - if (url) { - const decodedUri = decodeURIComponent(url); - const fragmentIndex = decodedUri.indexOf('#'); - if (fragmentIndex !== -1) { - const params = decodedUri.substring(fragmentIndex + 1); - const { code, state } = getParamsObject(params); - dispatch(verifyGoogleSignIn(code, state, deviceId)); - } - } - }; - // Sets deviceId useEffect(() => { if (!deviceId) { diff --git a/src/screens/login/index.tsx b/src/screens/login/index.tsx index 6b831ec6..f9e7004c 100644 --- a/src/screens/login/index.tsx +++ b/src/screens/login/index.tsx @@ -9,20 +9,32 @@ import Text from '../../../RN-UI-LIB/src/components/Text'; import TextInput from '../../../RN-UI-LIB/src/components/TextInput'; import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; import NaviLogoIcon from '../../../RN-UI-LIB/src/Icons/NaviLogoIcon'; -import { generateOTP, type GenerateOTPPayload, signInGoogle } from '../../action/authActions'; +import { + generateOTP, + verifyGoogleSignIn, + type GenerateOTPPayload, + handleGoogleLogout, +} from '../../action/authActions'; import { useAppDispatch } from '../../hooks'; import { type RootState } from '../../store/store'; import { addClickstreamEvent } from '../../services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; import Layout from '../layout/Layout'; -import { IS_SSO_ENABLED } from '../../constants/config'; +import { GOOGLE_SSO_CLIENT_ID, IS_SSO_ENABLED } from '../../constants/config'; import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import { GoogleSignin, User as GoogleSigninUser } from '@react-native-google-signin/google-signin'; +import { GenericType } from '../../common/GenericTypes'; +import { logError } from '../../components/utlis/errorUtils'; import GoogleIcon from '../../assets/icons/GoogleIcon'; interface ILoginForm { phoneNumber: string; } +GoogleSignin.configure({ + webClientId: GOOGLE_SSO_CLIENT_ID, +}); + function Login() { const { handleSubmit, @@ -54,8 +66,19 @@ function Login() { dispatch(generateOTP(data)); }; - const signInWithGoogle = () => { - dispatch(signInGoogle()); + const onPressGoogle = async () => { + try { + await GoogleSignin.hasPlayServices(); + const userInfo: GoogleSigninUser = await GoogleSignin.signIn(); + if (userInfo?.idToken) { + await dispatch(verifyGoogleSignIn(userInfo.idToken)); + return; + } + throw userInfo; + } catch (error: GenericType) { + await handleGoogleLogout(); + logError(error); + } }; return ( @@ -105,7 +128,7 @@ function Login() { testID="test_get_otp" textStyle={GenericStyles.fontSize16} /> - {IS_SSO_ENABLED ? ( + {IS_SSO_ENABLED && GOOGLE_SSO_CLIENT_ID ? ( <> @@ -118,11 +141,10 @@ function Login() {