Added fingerprinting fixed + update firebase fingerprint on log in BOD-132

This commit is contained in:
Patrick Fic
2020-05-21 13:17:59 -07:00
parent 2ab2a27d27
commit 201e85d7db
9 changed files with 279 additions and 147 deletions

View File

@@ -13,3 +13,4 @@
1. $ firebase deploy --only functions 1. $ firebase deploy --only functions
7. Add the allowed domains. 7. Add the allowed domains.
8. Update server variables including FIREBASE_ADMINSDK_JSON, FIREBASE_DATABASE_URL 8. Update server variables including FIREBASE_ADMINSDK_JSON, FIREBASE_DATABASE_URL
9. Create the firestore and copy the rules from dev for userinstances.

View File

@@ -4573,6 +4573,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>refresh</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>reset</name> <name>reset</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4746,6 +4767,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>instanceconflictext</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>instanceconflictitle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>loading</name> <name>loading</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -0,0 +1,27 @@
import React from "react";
import { Result, Button } from "antd";
import { useTranslation } from "react-i18next";
export default function ConflictComponent() {
const { t } = useTranslation();
return (
<div>
<Result
status="warning"
title={t("general.labels.instanceconflictitle")}
extra={
<div>
<div>{t("general.labels.instanceconflictext")}</div>
<Button
onClick={() => {
window.location.reload();
}}
>
{t("general.actions.refresh")}
</Button>
</div>
}
/>
</div>
);
}

View File

@@ -7,10 +7,15 @@ import ChatAffixContainer from "../../components/chat-affix/chat-affix.container
import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FcmNotification from "../../components/fcm-notification/fcm-notification.component"; import FcmNotification from "../../components/fcm-notification/fcm-notification.component";
import FooterComponent from "../../components/footer/footer.component"; import FooterComponent from "../../components/footer/footer.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectInstanceConflict } from "../../redux/user/user.selectors";
//Component Imports //Component Imports
import HeaderContainer from "../../components/header/header.container"; import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ConflictComponent from '../../components/conflict/conflict.component'
import "./manage.page.styles.scss"; import "./manage.page.styles.scss";
const ManageRootPage = lazy(() => const ManageRootPage = lazy(() =>
import("../manage-root/manage-root.page.container") import("../manage-root/manage-root.page.container")
@@ -91,7 +96,15 @@ const JobsClose = lazy(() => import("../jobs-close/jobs-close.container"));
const { Header, Content, Footer } = Layout; const { Header, Content, Footer } = Layout;
export default function Manage({ match }) { const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
conflict: selectInstanceConflict,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function Manage({ match, conflict }) {
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {
@@ -105,20 +118,29 @@ export default function Manage({ match }) {
</Header> </Header>
<Layout> <Layout>
<Content <Content
className='content-container' className="content-container"
style={{ padding: "0em 4em 4em" }}> style={{ padding: "0em 4em 4em" }}
>
<FcmNotification /> <FcmNotification />
<ErrorBoundary> <ErrorBoundary>
{conflict ? (
<ConflictComponent />
) : (
<Suspense <Suspense
fallback={ fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} /> <LoadingSpinner message={t("general.labels.loadingapp")} />
}> }
>
<BreadCrumbs /> <BreadCrumbs />
<EnterInvoiceModalContainer /> <EnterInvoiceModalContainer />
<EmailOverlayContainer /> <EmailOverlayContainer />
<TimeTicketModalContainer /> <TimeTicketModalContainer />
<PrintCenterModalContainer /> <PrintCenterModalContainer />
<Route exact path={`${match.path}`} component={ManageRootPage} /> <Route
exact
path={`${match.path}`}
component={ManageRootPage}
/>
<Route exact path={`${match.path}/jobs`} component={JobsPage} /> <Route exact path={`${match.path}/jobs`} component={JobsPage} />
<Switch> <Switch>
@@ -232,7 +254,11 @@ export default function Manage({ match }) {
path={`${match.path}/available`} path={`${match.path}/available`}
component={JobsAvailablePage} component={JobsAvailablePage}
/> />
<Route exact path={`${match.path}/shop/`} component={ShopPage} /> <Route
exact
path={`${match.path}/shop/`}
component={ShopPage}
/>
<Route <Route
exact exact
path={`${match.path}/shop/templates`} path={`${match.path}/shop/templates`}
@@ -244,6 +270,7 @@ export default function Manage({ match }) {
component={ShopVendorPageContainer} component={ShopVendorPageContainer}
/> />
</Suspense> </Suspense>
)}
</ErrorBoundary> </ErrorBoundary>
</Content> </Content>
</Layout> </Layout>
@@ -255,3 +282,4 @@ export default function Manage({ match }) {
</Layout> </Layout>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(Manage);

View File

@@ -12,6 +12,8 @@ const INITIAL_STATE = {
const userReducer = (state = INITIAL_STATE, action) => { const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) { switch (action.type) {
case UserActionTypes.SET_INSTANCE_ID:
return { ...state, conflict: false };
case UserActionTypes.SET_INSTANCE_CONFLICT: case UserActionTypes.SET_INSTANCE_CONFLICT:
return { ...state, conflict: true }; return { ...state, conflict: true };
case UserActionTypes.SIGN_IN_SUCCESS: case UserActionTypes.SIGN_IN_SUCCESS:

View File

@@ -26,7 +26,6 @@ export function* signInWithEmail({ payload: { email, password } }) {
const { user } = yield auth.signInWithEmailAndPassword(email, password); const { user } = yield auth.signInWithEmailAndPassword(email, password);
LogRocket.identify(user.email); LogRocket.identify(user.email);
yield put(setInstanceId("123"));
yield put( yield put(
signInSuccess({ signInSuccess({
uid: user.uid, uid: user.uid,
@@ -51,7 +50,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({
@@ -101,12 +100,11 @@ export function* setInstanceIdSaga({ payload: uid }) {
const userInstanceRef = firestore.doc(`userInstance/${uid}`); const userInstanceRef = firestore.doc(`userInstance/${uid}`);
const fingerprint = Fingerprint2.x64hash128( const fingerprint = Fingerprint2.x64hash128(
(yield Fingerprint2.getPromise({ (yield Fingerprint2.getPromise({})).map((c) => c.value).join(""),
excludes: { fonts: true, audio: true, webgl: true },
})).join(""),
31 31
); );
var result = window.confirm("Press a button!");
if (result)
yield userInstanceRef.set({ yield userInstanceRef.set({
timestamp: new Date(), timestamp: new Date(),
fingerprint, fingerprint,
@@ -129,22 +127,17 @@ export function* checkInstanceIdSaga({ payload: uid }) {
const userInstanceRef = firestore.doc(`userInstance/${uid}`); const userInstanceRef = firestore.doc(`userInstance/${uid}`);
const fingerprint = Fingerprint2.x64hash128( const fingerprint = Fingerprint2.x64hash128(
(yield Fingerprint2.getPromise({ (yield Fingerprint2.getPromise({})).map((c) => c.value).join(""),
excludes: { fonts: true, audio: true, webgl: true },
})).join(""),
31 31
); );
const snapshot = yield userInstanceRef.get(); const snapshot = yield userInstanceRef.get();
console.log("function*checkInstanceIdSaga -> snapshot", snapshot.data());
console.log("fingerprint", fingerprint);
if (snapshot.data().fingerprint === fingerprint) { if (snapshot.data().fingerprint === fingerprint) {
console.log("Waiting and checking."); yield delay(30000);
yield delay(5000);
yield put(checkInstanceId(uid)); yield put(checkInstanceId(uid));
} else { } else {
console.log("Didnt match"); console.log("ERROR: Fingerprints do not match. Conflict detected.");
yield put(setInstanceConflict()); yield put(setInstanceConflict());
} }
// yield userInstanceRef.set({ // yield userInstanceRef.set({
@@ -158,6 +151,14 @@ export function* checkInstanceIdSaga({ payload: uid }) {
} }
} }
export function* onSignInSuccess() {
yield takeLatest(UserActionTypes.SIGN_IN_SUCCESS, signInSuccessSaga);
}
export function* signInSuccessSaga({ payload }) {
yield put(setInstanceId(payload.uid));
}
export function* userSagas() { export function* userSagas() {
yield all([ yield all([
call(onEmailSignInStart), call(onEmailSignInStart),
@@ -166,5 +167,6 @@ export function* userSagas() {
call(onUpdateUserDetails), call(onUpdateUserDetails),
call(onSetInstanceId), call(onSetInstanceId),
call(onCheckInstanceId), call(onCheckInstanceId),
call(onSignInSuccess),
]); ]);
} }

View File

@@ -312,6 +312,7 @@
"create": "Create", "create": "Create",
"delete": "Delete", "delete": "Delete",
"edit": "Edit", "edit": "Edit",
"refresh": "Refresh",
"reset": "Reset to original.", "reset": "Reset to original.",
"save": "Save", "save": "Save",
"saveandnew": "Save and New", "saveandnew": "Save and New",
@@ -322,6 +323,8 @@
"areyousure": "Are you sure?", "areyousure": "Are you sure?",
"barcode": "Barcode", "barcode": "Barcode",
"in": "In", "in": "In",
"instanceconflictext": "Your $t(titles.app) account can only be used on one device at any given time. Refresh your session to take control.",
"instanceconflictitle": "Your account is being used elsewhere.",
"loading": "Loading...", "loading": "Loading...",
"loadingapp": "Loading Bodyshop.app", "loadingapp": "Loading Bodyshop.app",
"loadingshop": "Loading shop data...", "loadingshop": "Loading shop data...",

View File

@@ -312,6 +312,7 @@
"create": "", "create": "",
"delete": "Borrar", "delete": "Borrar",
"edit": "Editar", "edit": "Editar",
"refresh": "",
"reset": "Restablecer a original.", "reset": "Restablecer a original.",
"save": "Salvar", "save": "Salvar",
"saveandnew": "", "saveandnew": "",
@@ -322,6 +323,8 @@
"areyousure": "", "areyousure": "",
"barcode": "código de barras", "barcode": "código de barras",
"in": "en", "in": "en",
"instanceconflictext": "",
"instanceconflictitle": "",
"loading": "Cargando...", "loading": "Cargando...",
"loadingapp": "Cargando Bodyshop.app", "loadingapp": "Cargando Bodyshop.app",
"loadingshop": "Cargando datos de la tienda ...", "loadingshop": "Cargando datos de la tienda ...",

View File

@@ -312,6 +312,7 @@
"create": "", "create": "",
"delete": "Effacer", "delete": "Effacer",
"edit": "modifier", "edit": "modifier",
"refresh": "",
"reset": "Rétablir l'original.", "reset": "Rétablir l'original.",
"save": "sauvegarder", "save": "sauvegarder",
"saveandnew": "", "saveandnew": "",
@@ -322,6 +323,8 @@
"areyousure": "", "areyousure": "",
"barcode": "code à barre", "barcode": "code à barre",
"in": "dans", "in": "dans",
"instanceconflictext": "",
"instanceconflictitle": "",
"loading": "Chargement...", "loading": "Chargement...",
"loadingapp": "Chargement de Bodyshop.app", "loadingapp": "Chargement de Bodyshop.app",
"loadingshop": "Chargement des données de la boutique ...", "loadingshop": "Chargement des données de la boutique ...",