diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 00000000..ad37e2c2 --- /dev/null +++ b/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/src/stores/aiStore.js b/src/stores/aiStore.js index b95cf093..541ddeb1 100644 --- a/src/stores/aiStore.js +++ b/src/stores/aiStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; /** @@ -417,7 +418,7 @@ Format as an actionable plan that can be imported into a remediation tracker.`; } }), { - name: 'csf-ai-storage', + name: scopedKey('csf-ai-storage'), partialize: (state) => ({ llmProvider: state.llmProvider, dataMode: state.dataMode, diff --git a/src/stores/artifactStore.js b/src/stores/artifactStore.js index 24ac4c5e..8ee57887 100644 --- a/src/stores/artifactStore.js +++ b/src/stores/artifactStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; import Papa from 'papaparse'; import { v4 as uuidv4 } from 'uuid'; @@ -378,7 +379,7 @@ const useArtifactStore = create( } }), { - name: 'csf-artifacts-storage', + name: scopedKey('csf-artifacts-storage'), version: 5, migrate: (persistedState, version) => { // Version 2: Added link, complianceRequirement, controlId, type, jiraKey fields diff --git a/src/stores/assessmentsStore.js b/src/stores/assessmentsStore.js index c40d2adc..1ae34e13 100644 --- a/src/stores/assessmentsStore.js +++ b/src/stores/assessmentsStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; import Papa from 'papaparse'; import { v4 as uuidv4 } from 'uuid'; @@ -1562,7 +1563,7 @@ const useAssessmentsStore = create( } }), { - name: 'csf-assessments-storage', + name: scopedKey('csf-assessments-storage'), version: 7, migrate: (persistedState, version) => { // Version 1: Migrate observations to quarterly structure diff --git a/src/stores/auditLogStore.js b/src/stores/auditLogStore.js index fdfe3cf1..31294178 100644 --- a/src/stores/auditLogStore.js +++ b/src/stores/auditLogStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; import { v4 as uuidv4 } from 'uuid'; @@ -53,7 +54,7 @@ const useAuditLogStore = create( URL.revokeObjectURL(url); } }), - { name: 'csf-audit-log' } + { name: scopedKey('csf-audit-log') } ) ); diff --git a/src/stores/controlsStore.js b/src/stores/controlsStore.js index e7319399..000707f3 100644 --- a/src/stores/controlsStore.js +++ b/src/stores/controlsStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; import Papa from 'papaparse'; import { sanitizeInput, escapeCSVValue } from '../utils/sanitize'; @@ -471,7 +472,7 @@ const useControlsStore = create( } }), { - name: 'csf-controls-storage', + name: scopedKey('csf-controls-storage'), version: 5, migrate: (persistedState, version) => { // Version 5: Default controls from Alma Security example data diff --git a/src/stores/csfStore.js b/src/stores/csfStore.js index b2220d0b..dfc6cb49 100644 --- a/src/stores/csfStore.js +++ b/src/stores/csfStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; import { parseUserInfo, findOrCreateUser } from '../utils/userUtils'; import { sanitizeInput } from '../utils/sanitize'; @@ -252,7 +253,7 @@ const useCSFStore = create( }, }), { - name: 'csf-data-storage', + name: scopedKey('csf-data-storage'), version: 2, migrate: (persistedState, version) => { // Version 2: Force re-download of CSV to get proper owner assignments diff --git a/src/stores/evaluationsStore.js b/src/stores/evaluationsStore.js index 7d6523cc..2781e1cd 100644 --- a/src/stores/evaluationsStore.js +++ b/src/stores/evaluationsStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; import { sanitizeInput } from '../utils/sanitize'; @@ -453,7 +454,7 @@ const useEvaluationsStore = create( } }), { - name: 'csf-evaluations-storage', + name: scopedKey('csf-evaluations-storage'), version: 1, partialize: (state) => ({ evaluations: state.evaluations, diff --git a/src/stores/findingsStore.js b/src/stores/findingsStore.js index 3892ec50..06854000 100644 --- a/src/stores/findingsStore.js +++ b/src/stores/findingsStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; import Papa from 'papaparse'; import { v4 as uuidv4 } from 'uuid'; @@ -357,7 +358,7 @@ const useFindingsStore = create( } }), { - name: 'csf-findings-storage', + name: scopedKey('csf-findings-storage'), version: 3, migrate: (persistedState, version) => { // Version 2: Added default findings for new installations diff --git a/src/stores/frameworksStore.js b/src/stores/frameworksStore.js index e9049516..0da06643 100644 --- a/src/stores/frameworksStore.js +++ b/src/stores/frameworksStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; // Default frameworks - used for initial state and migrations @@ -194,7 +195,7 @@ const useFrameworksStore = create( } }), { - name: 'csf-frameworks-storage', + name: scopedKey('csf-frameworks-storage'), version: 5, migrate: (persistedState, version) => { // Version 2: Reset to new default frameworks (removed SOC2, HIPAA, PCI-DSS; updated names; added source) diff --git a/src/stores/requirementsStore.js b/src/stores/requirementsStore.js index 2711e1ce..142026b0 100644 --- a/src/stores/requirementsStore.js +++ b/src/stores/requirementsStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; import Papa from 'papaparse'; import { escapeCSVValue } from '../utils/sanitize'; @@ -418,7 +419,7 @@ const useRequirementsStore = create( } }), { - name: 'csf-requirements-storage', + name: scopedKey('csf-requirements-storage'), partialize: (state) => ({ requirements: state.requirements }) diff --git a/src/stores/uiStore.js b/src/stores/uiStore.js index 8450ba24..ca8aafcd 100644 --- a/src/stores/uiStore.js +++ b/src/stores/uiStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; const useUIStore = create( @@ -103,7 +104,7 @@ const useUIStore = create( }), }), { - name: 'csf-ui-storage', + name: scopedKey('csf-ui-storage'), partialize: (state) => ({ darkMode: state.darkMode, itemsPerPage: state.itemsPerPage, diff --git a/src/stores/userStore.js b/src/stores/userStore.js index 7bee9ef6..d48871f6 100644 --- a/src/stores/userStore.js +++ b/src/stores/userStore.js @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { scopedKey } from '../utils/visitorId'; import { persist } from 'zustand/middleware'; import { v4 as uuidv4 } from 'uuid'; @@ -132,7 +133,7 @@ const useUserStore = create( }, }), { - name: 'csf-users-storage', + name: scopedKey('csf-users-storage'), version: 2, migrate: (persistedState, version) => { // Version 2: Ensure default users have stable IDs diff --git a/src/utils/visitorId.js b/src/utils/visitorId.js new file mode 100644 index 00000000..c1f8b079 --- /dev/null +++ b/src/utils/visitorId.js @@ -0,0 +1,15 @@ +// Generate a stable per-visitor ID scoped to this browser session. +// This prevents localStorage cross-contamination between demo visitors. +const VISITOR_KEY = 'csf-visitor-id'; + +function getVisitorId() { + let id = localStorage.getItem(VISITOR_KEY); + if (!id) { + id = 'v-' + Math.random().toString(36).slice(2, 11); + localStorage.setItem(VISITOR_KEY, id); + } + return id; +} + +export const visitorId = getVisitorId(); +export const scopedKey = (name) => `${name}-${visitorId}`;