feat(consent): enforce GDPR storage classes for cookie/localStorage data - #1248
Merged
Merged
Conversation
Add a storage-class enforcement layer on top of the existing cookie consent store: storage keys declare which consent category (necessary, analytics, functional, marketing) they belong to, reads/writes are gated by the user's current consent, and any localStorage, sessionStorage, or cookie entry tied to a revoked category is purged automatically (on load and on every consent change). Closes rinafcode#366.
|
@Rafiat30 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Contributor
|
Thank you for contributing to the project |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #366
Summary
The existing cookie consent store (
src/lib/consent/store.ts) already tracks per-category consent (necessary,analytics,functional,marketing), but nothing tied that decision to the app's actual browser storage. Any feature could write tolocalStorage/cookies regardless of consent, and revoking a category didn't clear anything already written — a gap against GDPR Art. 7(3), which requires that withdrawing consent be as effective as giving it.This PR adds a Storage Classes enforcement layer on top of the existing consent store: storage keys declare which consent category they belong to, reads/writes are gated by current consent, and storage tied to a revoked category is purged automatically.
What's implemented
src/lib/consent/storageClasses.ts(new) — the core engine:registerStorageKey/unregisterStorageKey/getStorageClass/listRegisteredStorageKeys/clearStorageClassRegistry— a registry mapping storage keys to{ category, area }.isStorageClassAllowed(category)— checks the category against the liveuseConsentStorestate (necessaryis always allowed).setClassifiedItem(descriptor, value)— registers the key and writes tolocalStorage,sessionStorage, or a cookie, but only if the category is currently consented to. Returns whether the write happened.getClassifiedItem(key)— returnsnullfor unregistered keys and for keys whose category is no longer consented to, even if a stale value is still physically present.removeClassifiedItem(key)— removes a key's value from its underlying storage area.purgeDisallowedStorage()— removes every registered entry whose category is no longer allowed; returns the purged keys.enforceStorageClasses()— purges immediately, then re-purges on every consent-store change viauseConsentStore.subscribe; returns an unsubscribe function.useStorageClassEnforcement()— a React hook wrappingenforceStorageClasses()in auseEffect.src/components/consent/CookieConsentBanner.tsx(modified) — callsuseStorageClassEnforcement(). This component is always mounted (viaRootProviders), so enforcement runs for the whole app lifetime: on load it purges any storage left over from a category the user has since revoked, and it re-purges automatically whenever preferences change inCookiePreferencesModal(accept all / reject all / save custom).docs/GDPR_STORAGE_CLASSES.md(new) — explains the problem, the API, the enforcement integration point, and how to run the tests.Tests added
src/lib/consent/__tests__/storageClasses.test.ts(new, 25 tests) — covers registry CRUD,isStorageClassAllowedfor all categories (including default-denied and accept/reject transitions), gatedsetClassifiedItem/getClassifiedItemround-trips across all three storage areas (localStorage,sessionStorage,cookie), stale-value handling after consent withdrawal,removeClassifiedItem,purgeDisallowedStorage(multi-area purge, no-op cases), andenforceStorageClasses(immediate purge on start, automatic re-purge on consent change, and that purging stops after unsubscribe).src/components/consent/__tests__/CookieConsentBanner.test.tsx(new, 2 tests) — verifies the banner purges storage for an already-revoked category on mount, and that it still renders normally.All new and existing consent/GDPR-related tests pass (41/41 across
src/lib/consent,src/components/consent, the legacyCookieConsentBannertest, anduseGdprConsenttest).tsc --noEmitpasses with no errors. No existing files' behavior was changed beyond the one-line hook addition inCookieConsentBanner.tsx.How to test
pnpm install pnpm test src/lib/consent src/components/consent pnpm run type-checkManually: open the app, accept only "Analytics" in the cookie preferences modal, use dev tools to check that an item written via
setClassifiedItem({ key, category: 'marketing', area: 'localStorage' }, value)is not written; then accept "Marketing" and confirm it is written; then reject it again and confirm the entry disappears fromlocalStoragewithout a page reload.