Added fingerprinting + store in firebase for user auth. BOD-132

This commit is contained in:
Patrick Fic
2020-05-21 09:46:30 -07:00
parent 8be8ad0ed9
commit 2ab2a27d27
8 changed files with 142 additions and 40 deletions

View File

@@ -17,6 +17,7 @@
"axios": "^0.19.2", "axios": "^0.19.2",
"dinero.js": "^1.8.1", "dinero.js": "^1.8.1",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"fingerprintjs2": "^2.1.0",
"firebase": "^7.14.3", "firebase": "^7.14.3",
"graphql": "^15.0.0", "graphql": "^15.0.0",
"i18next": "^19.4.4", "i18next": "^19.4.4",

View File

@@ -21,7 +21,7 @@ class FcmNotificationComponent extends Component {
.requestPermission() .requestPermission()
.then(async function () { .then(async function () {
const token = await messaging.getToken(); const token = await messaging.getToken();
console.log("Instance Token", token);
client.mutate({ client.mutate({
mutation: UPDATE_FCM_TOKEN, mutation: UPDATE_FCM_TOKEN,
variables: { authEmail: currentUser.email, token: { [token]: true } }, variables: { authEmail: currentUser.email, token: { [token]: true } },

View File

@@ -1,55 +1,69 @@
import UserActionTypes from "./user.types"; import UserActionTypes from "./user.types";
export const signInSuccess = user => ({ export const signInSuccess = (user) => ({
type: UserActionTypes.SIGN_IN_SUCCESS, type: UserActionTypes.SIGN_IN_SUCCESS,
payload: user payload: user,
}); });
export const signInFailure = errorMsg => ({ export const signInFailure = (errorMsg) => ({
type: UserActionTypes.SIGN_IN_FAILURE, type: UserActionTypes.SIGN_IN_FAILURE,
payload: errorMsg payload: errorMsg,
}); });
export const emailSignInStart = emailAndPassword => ({ export const emailSignInStart = (emailAndPassword) => ({
type: UserActionTypes.EMAIL_SIGN_IN_START, type: UserActionTypes.EMAIL_SIGN_IN_START,
payload: emailAndPassword payload: emailAndPassword,
}); });
export const checkUserSession = () => ({ export const checkUserSession = () => ({
type: UserActionTypes.CHECK_USER_SESSION type: UserActionTypes.CHECK_USER_SESSION,
}); });
export const signOutStart = () => ({ export const signOutStart = () => ({
type: UserActionTypes.SIGN_OUT_START type: UserActionTypes.SIGN_OUT_START,
}); });
export const signOutSuccess = () => ({ 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, type: UserActionTypes.SIGN_OUT_FAILURE,
payload: error payload: error,
}); });
export const unauthorizedUser = () => ({ export const unauthorizedUser = () => ({
type: UserActionTypes.UNAUTHORIZED_USER type: UserActionTypes.UNAUTHORIZED_USER,
}); });
export const setUserLanguage = language => ({ export const setUserLanguage = (language) => ({
type: UserActionTypes.SET_USER_LANGUAGE, type: UserActionTypes.SET_USER_LANGUAGE,
payload: language payload: language,
}); });
export const updateUserDetails = userDetails => ({ export const updateUserDetails = (userDetails) => ({
type: UserActionTypes.UPDATE_USER_DETAILS, type: UserActionTypes.UPDATE_USER_DETAILS,
payload: userDetails payload: userDetails,
}); });
export const updateUserDetailsSuccess = userDetails => ({ export const updateUserDetailsSuccess = (userDetails) => ({
type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS, type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS,
payload: userDetails payload: userDetails,
}); });
export const setBodyshop = bodyshop => ({ export const setBodyshop = (bodyshop) => ({
type: UserActionTypes.SET_SHOP_DETAILS, 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,
}); });

View File

@@ -6,41 +6,44 @@ const INITIAL_STATE = {
//language: "en-US" //language: "en-US"
}, },
bodyshop: null, bodyshop: null,
error: null error: null,
conflict: false,
}; };
const userReducer = (state = INITIAL_STATE, action) => { const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) { switch (action.type) {
case UserActionTypes.SET_INSTANCE_CONFLICT:
return { ...state, conflict: true };
case UserActionTypes.SIGN_IN_SUCCESS: case UserActionTypes.SIGN_IN_SUCCESS:
return { return {
...state, ...state,
currentUser: action.payload, currentUser: action.payload,
error: null error: null,
}; };
case UserActionTypes.SIGN_OUT_SUCCESS: case UserActionTypes.SIGN_OUT_SUCCESS:
return { return {
...state, ...state,
currentUser: { authorized: false }, currentUser: { authorized: false },
error: null error: null,
}; };
case UserActionTypes.UNAUTHORIZED_USER: case UserActionTypes.UNAUTHORIZED_USER:
return { return {
...state, ...state,
error: null, error: null,
currentUser: { authorized: false } currentUser: { authorized: false },
}; };
case UserActionTypes.SET_USER_LANGUAGE: case UserActionTypes.SET_USER_LANGUAGE:
return { return {
...state, ...state,
language: action.payload language: action.payload,
}; };
case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS: case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS:
return { return {
...state, ...state,
currentUser: { currentUser: {
...state.currentUser, ...state.currentUser,
...action.payload //Spread current user details in. ...action.payload, //Spread current user details in.
} },
}; };
case UserActionTypes.SET_SHOP_DETAILS: case UserActionTypes.SET_SHOP_DETAILS:
@@ -50,7 +53,7 @@ const userReducer = (state = INITIAL_STATE, action) => {
case UserActionTypes.EMAIL_SIGN_UP_FAILURE: case UserActionTypes.EMAIL_SIGN_UP_FAILURE:
return { return {
...state, ...state,
error: action.payload error: action.payload,
}; };
default: default:
return state; return state;

View File

@@ -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 LogRocket from "logrocket";
import { firestore } from "../../firebase/firebase.utils";
import Fingerprint2 from "fingerprintjs2";
import { import {
auth, auth,
getCurrentUser, getCurrentUser,
@@ -12,12 +15,18 @@ import {
signOutSuccess, signOutSuccess,
unauthorizedUser, unauthorizedUser,
updateUserDetailsSuccess, updateUserDetailsSuccess,
setInstanceId,
checkInstanceId,
setInstanceConflict,
} from "./user.actions"; } from "./user.actions";
import UserActionTypes from "./user.types"; import UserActionTypes from "./user.types";
export function* signInWithEmail({ payload: { email, password } }) { export function* signInWithEmail({ payload: { email, password } }) {
try { try {
const { user } = yield auth.signInWithEmailAndPassword(email, password); const { user } = yield auth.signInWithEmailAndPassword(email, password);
LogRocket.identify(user.email);
yield put(setInstanceId("123"));
yield put( yield put(
signInSuccess({ signInSuccess({
uid: user.uid, uid: user.uid,
@@ -35,7 +44,6 @@ export function* signInWithEmail({ payload: { email, password } }) {
export function* onEmailSignInStart() { export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail); yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
} }
export function* isUserAuthenticated() { export function* isUserAuthenticated() {
try { try {
const user = yield getCurrentUser(); const user = yield getCurrentUser();
@@ -43,6 +51,7 @@ export function* isUserAuthenticated() {
yield put(unauthorizedUser()); yield put(unauthorizedUser());
return; return;
} }
yield put(setInstanceId(user.uid));
LogRocket.identify(user.email); LogRocket.identify(user.email);
yield put( yield put(
signInSuccess({ signInSuccess({
@@ -57,11 +66,9 @@ export function* isUserAuthenticated() {
yield put(signInFailure(error)); yield put(signInFailure(error));
} }
} }
export function* onCheckUserSession() { export function* onCheckUserSession() {
yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated); yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
} }
export function* signOutStart() { export function* signOutStart() {
try { try {
yield auth.signOut(); yield auth.signOut();
@@ -71,11 +78,9 @@ export function* signOutStart() {
yield put(signOutFailure(error.message)); yield put(signOutFailure(error.message));
} }
} }
export function* onSignOutStart() { export function* onSignOutStart() {
yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart); yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
} }
export function* onUpdateUserDetails() { export function* onUpdateUserDetails() {
yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails); yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails);
} }
@@ -88,6 +93,70 @@ export function* updateUserDetails(userDetails) {
//TODO error handling //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() { export function* userSagas() {
yield all([ yield all([
@@ -95,5 +164,7 @@ export function* userSagas() {
call(onCheckUserSession), call(onCheckUserSession),
call(onSignOutStart), call(onSignOutStart),
call(onUpdateUserDetails), call(onUpdateUserDetails),
call(onSetInstanceId),
call(onCheckInstanceId),
]); ]);
} }

View File

@@ -1,18 +1,23 @@
import { createSelector } from "reselect"; import { createSelector } from "reselect";
const selectUser = state => state.user; const selectUser = (state) => state.user;
export const selectCurrentUser = createSelector( export const selectCurrentUser = createSelector(
[selectUser], [selectUser],
user => user.currentUser (user) => user.currentUser
); );
export const selectSignInError = createSelector( export const selectSignInError = createSelector(
[selectUser], [selectUser],
user => user.error (user) => user.error
); );
export const selectBodyshop = createSelector( export const selectBodyshop = createSelector(
[selectUser], [selectUser],
user => user.bodyshop (user) => user.bodyshop
);
export const selectInstanceConflict = createSelector(
[selectUser],
(user) => user.conflict
); );

View File

@@ -15,6 +15,9 @@ const UserActionTypes = {
SET_USER_LANGUAGE: "SET_USER_LANGUAGE", SET_USER_LANGUAGE: "SET_USER_LANGUAGE",
UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS", UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS",
UPDATE_USER_DETAILS_SUCCESS: "UPDATE_USER_DETAILS_SUCCESS", 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; export default UserActionTypes;

View File

@@ -5924,6 +5924,11 @@ find-up@^3.0.0:
dependencies: dependencies:
locate-path "^3.0.0" 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: firebase@^7.14.3:
version "7.14.3" version "7.14.3"
resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.14.3.tgz#dfe6fa3e5982a6d6d6d44bfc50dba23568a5a777" resolved "https://registry.yarnpkg.com/firebase/-/firebase-7.14.3.tgz#dfe6fa3e5982a6d6d6d44bfc50dba23568a5a777"