diff --git a/client/package.json b/client/package.json index 206234f61..8a90e2dc3 100644 --- a/client/package.json +++ b/client/package.json @@ -17,6 +17,7 @@ "axios": "^0.19.2", "dinero.js": "^1.8.1", "dotenv": "^8.2.0", + "fingerprintjs2": "^2.1.0", "firebase": "^7.14.3", "graphql": "^15.0.0", "i18next": "^19.4.4", diff --git a/client/src/components/fcm-notification/fcm-notification.component.jsx b/client/src/components/fcm-notification/fcm-notification.component.jsx index d33254f05..a939c5dfa 100644 --- a/client/src/components/fcm-notification/fcm-notification.component.jsx +++ b/client/src/components/fcm-notification/fcm-notification.component.jsx @@ -21,7 +21,7 @@ class FcmNotificationComponent extends Component { .requestPermission() .then(async function () { const token = await messaging.getToken(); - + console.log("Instance Token", token); client.mutate({ mutation: UPDATE_FCM_TOKEN, variables: { authEmail: currentUser.email, token: { [token]: true } }, diff --git a/client/src/redux/user/user.actions.js b/client/src/redux/user/user.actions.js index 2d6989122..88fdc5fd6 100644 --- a/client/src/redux/user/user.actions.js +++ b/client/src/redux/user/user.actions.js @@ -1,55 +1,69 @@ import UserActionTypes from "./user.types"; -export const signInSuccess = user => ({ +export const signInSuccess = (user) => ({ type: UserActionTypes.SIGN_IN_SUCCESS, - payload: user + payload: user, }); -export const signInFailure = errorMsg => ({ +export const signInFailure = (errorMsg) => ({ type: UserActionTypes.SIGN_IN_FAILURE, - payload: errorMsg + payload: errorMsg, }); -export const emailSignInStart = emailAndPassword => ({ +export const emailSignInStart = (emailAndPassword) => ({ type: UserActionTypes.EMAIL_SIGN_IN_START, - payload: emailAndPassword + payload: emailAndPassword, }); export const checkUserSession = () => ({ - type: UserActionTypes.CHECK_USER_SESSION + type: UserActionTypes.CHECK_USER_SESSION, }); export const signOutStart = () => ({ - type: UserActionTypes.SIGN_OUT_START + type: UserActionTypes.SIGN_OUT_START, }); export const signOutSuccess = () => ({ - type: UserActionTypes.SIGN_OUT_SUCCESS + type: UserActionTypes.SIGN_OUT_SUCCESS, }); -export const signOutFailure = error => ({ +export const signOutFailure = (error) => ({ type: UserActionTypes.SIGN_OUT_FAILURE, - payload: error + payload: error, }); export const unauthorizedUser = () => ({ - type: UserActionTypes.UNAUTHORIZED_USER + type: UserActionTypes.UNAUTHORIZED_USER, }); -export const setUserLanguage = language => ({ +export const setUserLanguage = (language) => ({ type: UserActionTypes.SET_USER_LANGUAGE, - payload: language + payload: language, }); -export const updateUserDetails = userDetails => ({ +export const updateUserDetails = (userDetails) => ({ type: UserActionTypes.UPDATE_USER_DETAILS, - payload: userDetails + payload: userDetails, }); -export const updateUserDetailsSuccess = userDetails => ({ +export const updateUserDetailsSuccess = (userDetails) => ({ type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS, - payload: userDetails + payload: userDetails, }); -export const setBodyshop = bodyshop => ({ +export const setBodyshop = (bodyshop) => ({ type: UserActionTypes.SET_SHOP_DETAILS, - payload: bodyshop + payload: bodyshop, +}); + +export const setInstanceId = (userInfo) => ({ + type: UserActionTypes.SET_INSTANCE_ID, + payload: userInfo, +}); + +export const checkInstanceId = (uid) => ({ + type: UserActionTypes.CHECK_INSTANCE_ID, + payload: uid, +}); + +export const setInstanceConflict = () => ({ + type: UserActionTypes.SET_INSTANCE_CONFLICT, }); diff --git a/client/src/redux/user/user.reducer.js b/client/src/redux/user/user.reducer.js index 880ff98d0..d263b5fc8 100644 --- a/client/src/redux/user/user.reducer.js +++ b/client/src/redux/user/user.reducer.js @@ -6,41 +6,44 @@ const INITIAL_STATE = { //language: "en-US" }, bodyshop: null, - error: null + error: null, + conflict: false, }; const userReducer = (state = INITIAL_STATE, action) => { switch (action.type) { + case UserActionTypes.SET_INSTANCE_CONFLICT: + return { ...state, conflict: true }; case UserActionTypes.SIGN_IN_SUCCESS: return { ...state, currentUser: action.payload, - error: null + error: null, }; case UserActionTypes.SIGN_OUT_SUCCESS: return { ...state, currentUser: { authorized: false }, - error: null + error: null, }; case UserActionTypes.UNAUTHORIZED_USER: return { ...state, error: null, - currentUser: { authorized: false } + currentUser: { authorized: false }, }; case UserActionTypes.SET_USER_LANGUAGE: return { ...state, - language: action.payload + language: action.payload, }; case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS: return { ...state, currentUser: { ...state.currentUser, - ...action.payload //Spread current user details in. - } + ...action.payload, //Spread current user details in. + }, }; case UserActionTypes.SET_SHOP_DETAILS: @@ -50,7 +53,7 @@ const userReducer = (state = INITIAL_STATE, action) => { case UserActionTypes.EMAIL_SIGN_UP_FAILURE: return { ...state, - error: action.payload + error: action.payload, }; default: return state; diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js index 465a9d878..4f615d5b9 100644 --- a/client/src/redux/user/user.sagas.js +++ b/client/src/redux/user/user.sagas.js @@ -1,5 +1,8 @@ -import { all, call, put, takeLatest } from "redux-saga/effects"; +import { all, call, put, takeLatest, delay } from "redux-saga/effects"; import LogRocket from "logrocket"; +import { firestore } from "../../firebase/firebase.utils"; +import Fingerprint2 from "fingerprintjs2"; + import { auth, getCurrentUser, @@ -12,12 +15,18 @@ import { signOutSuccess, unauthorizedUser, updateUserDetailsSuccess, + setInstanceId, + checkInstanceId, + setInstanceConflict, } from "./user.actions"; import UserActionTypes from "./user.types"; export function* signInWithEmail({ payload: { email, password } }) { try { const { user } = yield auth.signInWithEmailAndPassword(email, password); + LogRocket.identify(user.email); + + yield put(setInstanceId("123")); yield put( signInSuccess({ uid: user.uid, @@ -35,7 +44,6 @@ export function* signInWithEmail({ payload: { email, password } }) { export function* onEmailSignInStart() { yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail); } - export function* isUserAuthenticated() { try { const user = yield getCurrentUser(); @@ -43,6 +51,7 @@ export function* isUserAuthenticated() { yield put(unauthorizedUser()); return; } + yield put(setInstanceId(user.uid)); LogRocket.identify(user.email); yield put( signInSuccess({ @@ -57,11 +66,9 @@ export function* isUserAuthenticated() { yield put(signInFailure(error)); } } - export function* onCheckUserSession() { yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated); } - export function* signOutStart() { try { yield auth.signOut(); @@ -71,11 +78,9 @@ export function* signOutStart() { yield put(signOutFailure(error.message)); } } - export function* onSignOutStart() { yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart); } - export function* onUpdateUserDetails() { yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails); } @@ -88,6 +93,70 @@ export function* updateUserDetails(userDetails) { //TODO error handling } } +export function* onSetInstanceId() { + yield takeLatest(UserActionTypes.SET_INSTANCE_ID, setInstanceIdSaga); +} +export function* setInstanceIdSaga({ payload: uid }) { + try { + const userInstanceRef = firestore.doc(`userInstance/${uid}`); + + const fingerprint = Fingerprint2.x64hash128( + (yield Fingerprint2.getPromise({ + excludes: { fonts: true, audio: true, webgl: true }, + })).join(""), + 31 + ); + + yield userInstanceRef.set({ + timestamp: new Date(), + fingerprint, + }); + + yield delay(5000); + yield put(checkInstanceId(uid)); + } catch (error) { + console.log("error", error); + //yield put(signOutFailure(error.message)); + //TODO error handling + } +} + +export function* onCheckInstanceId() { + yield takeLatest(UserActionTypes.CHECK_INSTANCE_ID, checkInstanceIdSaga); +} +export function* checkInstanceIdSaga({ payload: uid }) { + try { + const userInstanceRef = firestore.doc(`userInstance/${uid}`); + + const fingerprint = Fingerprint2.x64hash128( + (yield Fingerprint2.getPromise({ + excludes: { fonts: true, audio: true, webgl: true }, + })).join(""), + 31 + ); + + const snapshot = yield userInstanceRef.get(); + console.log("function*checkInstanceIdSaga -> snapshot", snapshot.data()); + console.log("fingerprint", fingerprint); + + if (snapshot.data().fingerprint === fingerprint) { + console.log("Waiting and checking."); + yield delay(5000); + yield put(checkInstanceId(uid)); + } else { + console.log("Didnt match"); + yield put(setInstanceConflict()); + } + // yield userInstanceRef.set({ + // timestamp: new Date(), + // fingerprint, + // }); + } catch (error) { + console.log("error", error); + //yield put(signOutFailure(error.message)); + //TODO error handling + } +} export function* userSagas() { yield all([ @@ -95,5 +164,7 @@ export function* userSagas() { call(onCheckUserSession), call(onSignOutStart), call(onUpdateUserDetails), + call(onSetInstanceId), + call(onCheckInstanceId), ]); } diff --git a/client/src/redux/user/user.selectors.js b/client/src/redux/user/user.selectors.js index 4384f7ed6..17b3f4e61 100644 --- a/client/src/redux/user/user.selectors.js +++ b/client/src/redux/user/user.selectors.js @@ -1,18 +1,23 @@ import { createSelector } from "reselect"; -const selectUser = state => state.user; +const selectUser = (state) => state.user; export const selectCurrentUser = createSelector( [selectUser], - user => user.currentUser + (user) => user.currentUser ); export const selectSignInError = createSelector( [selectUser], - user => user.error + (user) => user.error ); export const selectBodyshop = createSelector( [selectUser], - user => user.bodyshop + (user) => user.bodyshop +); + +export const selectInstanceConflict = createSelector( + [selectUser], + (user) => user.conflict ); diff --git a/client/src/redux/user/user.types.js b/client/src/redux/user/user.types.js index febfe5eab..7af447fb9 100644 --- a/client/src/redux/user/user.types.js +++ b/client/src/redux/user/user.types.js @@ -15,6 +15,9 @@ const UserActionTypes = { SET_USER_LANGUAGE: "SET_USER_LANGUAGE", UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS", UPDATE_USER_DETAILS_SUCCESS: "UPDATE_USER_DETAILS_SUCCESS", - SET_SHOP_DETAILS: "SET_SHOP_DETAILS" + SET_SHOP_DETAILS: "SET_SHOP_DETAILS", + SET_INSTANCE_ID: "SET_INSTANCE_ID", + CHECK_INSTANCE_ID: "CHECK_INSTANCE_ID", + SET_INSTANCE_CONFLICT: "SET_INSTANCE_CONFLICT" }; export default UserActionTypes; diff --git a/client/yarn.lock b/client/yarn.lock index 99f8ba28f..a3279f8d5 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -5924,6 +5924,11 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +fingerprintjs2@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fingerprintjs2/-/fingerprintjs2-2.1.0.tgz#21dc3fee27d3b199056ef8eb873debccd8e06323" + integrity sha512-H1k/ESTD2rJ3liupyqWBPjZC+LKfCGixQzz/NDN4dkgbmG1bVFyMOh7luKSkVDoyfhgvRm62pviNMPI+eJTZcQ== + firebase@^7.14.3: version "7.14.3" resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.14.3.tgz#dfe6fa3e5982a6d6d6d44bfc50dba23568a5a777"