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

@@ -12,4 +12,5 @@
6. Deploy the function
1. $ firebase deploy --only functions
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>
</translations>
</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>
<name>reset</name>
<definition_loaded>false</definition_loaded>
@@ -4746,6 +4767,48 @@
</translation>
</translations>
</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>
<name>loading</name>
<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 FcmNotification from "../../components/fcm-notification/fcm-notification.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
import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ConflictComponent from '../../components/conflict/conflict.component'
import "./manage.page.styles.scss";
const ManageRootPage = lazy(() =>
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;
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();
useEffect(() => {
@@ -105,145 +118,159 @@ export default function Manage({ match }) {
</Header>
<Layout>
<Content
className='content-container'
style={{ padding: "0em 4em 4em" }}>
className="content-container"
style={{ padding: "0em 4em 4em" }}
>
<FcmNotification />
<ErrorBoundary>
<Suspense
fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} />
}>
<BreadCrumbs />
<EnterInvoiceModalContainer />
<EmailOverlayContainer />
<TimeTicketModalContainer />
<PrintCenterModalContainer />
<Route exact path={`${match.path}`} component={ManageRootPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
{conflict ? (
<ConflictComponent />
) : (
<Suspense
fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} />
}
>
<BreadCrumbs />
<EnterInvoiceModalContainer />
<EmailOverlayContainer />
<TimeTicketModalContainer />
<PrintCenterModalContainer />
<Route
exact
path={`${match.path}`}
component={ManageRootPage}
/>
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
<Switch>
<Switch>
<Route
exact
path={`${match.path}/jobs/:jobId/intake`}
component={JobIntake}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/close`}
component={JobsClose}
/>
<Route
exact
path={`${match.path}/jobs/all`}
component={AllJobs}
/>
<Route
exact
path={`${match.path}/jobs/new`}
component={JobsCreateContainerPage}
/>
<Route
path={`${match.path}/jobs/:jobId`}
component={JobsDetailPage}
/>
</Switch>
<Route
exact
path={`${match.path}/jobs/:jobId/intake`}
component={JobIntake}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/close`}
component={JobsClose}
/>
<Route
exact
path={`${match.path}/jobs/all`}
component={AllJobs}
/>
<Route
exact
path={`${match.path}/jobs/new`}
component={JobsCreateContainerPage}
/>
<Route
path={`${match.path}/jobs/:jobId`}
component={JobsDetailPage}
/>
</Switch>
<Route
exact
path={`${match.path}/courtesycars/`}
component={CourtesyCarsPage}
/>
<Switch>
<Route
exact
path={`${match.path}/courtesycars/new`}
component={CourtesyCarCreateContainer}
path={`${match.path}/courtesycars/`}
component={CourtesyCarsPage}
/>
<Switch>
<Route
exact
path={`${match.path}/courtesycars/new`}
component={CourtesyCarCreateContainer}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts`}
component={ContractsList}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts`}
component={ContractsList}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/new`}
component={ContractCreatePage}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/:contractId`}
component={ContractDetailPage}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/new`}
component={ContractCreatePage}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/:contractId`}
component={ContractDetailPage}
/>
<Route
exact
path={`${match.path}/courtesycars/:ccId`}
component={CourtesyCarDetailContainer}
/>
</Switch>
<Route
exact
path={`${match.path}/courtesycars/:ccId`}
component={CourtesyCarDetailContainer}
path={`${match.path}/profile`}
component={ProfilePage}
/>
</Switch>
<Route
exact
path={`${match.path}/profile`}
component={ProfilePage}
/>
<Route
exact
path={`${match.path}/vehicles`}
component={VehiclesContainer}
/>
<Route
exact
path={`${match.path}/production/list`}
component={ProductionListPage}
/>
<Route
exact
path={`${match.path}/production/board`}
component={ProductionBoardPage}
/>
<Route
exact
path={`${match.path}/vehicles/:vehId`}
component={VehiclesDetailContainer}
/>
<Route
exact
path={`${match.path}/invoices`}
component={InvoicesListPage}
/>
<Route
exact
path={`${match.path}/owners`}
component={OwnersContainer}
/>
<Route
exact
path={`${match.path}/owners/:ownerId`}
component={OwnersDetailContainer}
/>
<Route
exact
path={`${match.path}/schedule`}
component={ScheduleContainer}
/>
<Route
exact
path={`${match.path}/available`}
component={JobsAvailablePage}
/>
<Route exact path={`${match.path}/shop/`} component={ShopPage} />
<Route
exact
path={`${match.path}/shop/templates`}
component={ShopTemplates}
/>
<Route
exact
path={`${match.path}/shop/vendors`}
component={ShopVendorPageContainer}
/>
</Suspense>
<Route
exact
path={`${match.path}/vehicles`}
component={VehiclesContainer}
/>
<Route
exact
path={`${match.path}/production/list`}
component={ProductionListPage}
/>
<Route
exact
path={`${match.path}/production/board`}
component={ProductionBoardPage}
/>
<Route
exact
path={`${match.path}/vehicles/:vehId`}
component={VehiclesDetailContainer}
/>
<Route
exact
path={`${match.path}/invoices`}
component={InvoicesListPage}
/>
<Route
exact
path={`${match.path}/owners`}
component={OwnersContainer}
/>
<Route
exact
path={`${match.path}/owners/:ownerId`}
component={OwnersDetailContainer}
/>
<Route
exact
path={`${match.path}/schedule`}
component={ScheduleContainer}
/>
<Route
exact
path={`${match.path}/available`}
component={JobsAvailablePage}
/>
<Route
exact
path={`${match.path}/shop/`}
component={ShopPage}
/>
<Route
exact
path={`${match.path}/shop/templates`}
component={ShopTemplates}
/>
<Route
exact
path={`${match.path}/shop/vendors`}
component={ShopVendorPageContainer}
/>
</Suspense>
)}
</ErrorBoundary>
</Content>
</Layout>
@@ -255,3 +282,4 @@ export default function Manage({ match }) {
</Layout>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Manage);

View File

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

View File

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

View File

@@ -312,6 +312,7 @@
"create": "Create",
"delete": "Delete",
"edit": "Edit",
"refresh": "Refresh",
"reset": "Reset to original.",
"save": "Save",
"saveandnew": "Save and New",
@@ -322,6 +323,8 @@
"areyousure": "Are you sure?",
"barcode": "Barcode",
"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...",
"loadingapp": "Loading Bodyshop.app",
"loadingshop": "Loading shop data...",

View File

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

View File

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