Skip to content

feat(consent): enforce GDPR storage classes for cookie/localStorage data - #1248

Merged
RUKAYAT-CODER merged 1 commit into
rinafcode:mainfrom
Rafiat30:feat/gdpr-storage-classes
Aug 26, 2026
Merged

feat(consent): enforce GDPR storage classes for cookie/localStorage data#1248
RUKAYAT-CODER merged 1 commit into
rinafcode:mainfrom
Rafiat30:feat/gdpr-storage-classes

Conversation

@Rafiat30

Copy link
Copy Markdown
Contributor

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 to localStorage/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 live useConsentStore state (necessary is always allowed).
    • setClassifiedItem(descriptor, value) — registers the key and writes to localStorage, sessionStorage, or a cookie, but only if the category is currently consented to. Returns whether the write happened.
    • getClassifiedItem(key) — returns null for 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 via useConsentStore.subscribe; returns an unsubscribe function.
    • useStorageClassEnforcement() — a React hook wrapping enforceStorageClasses() in a useEffect.
  • src/components/consent/CookieConsentBanner.tsx (modified) — calls useStorageClassEnforcement(). This component is always mounted (via RootProviders), 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 in CookiePreferencesModal (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, isStorageClassAllowed for all categories (including default-denied and accept/reject transitions), gated setClassifiedItem/getClassifiedItem round-trips across all three storage areas (localStorage, sessionStorage, cookie), stale-value handling after consent withdrawal, removeClassifiedItem, purgeDisallowedStorage (multi-area purge, no-op cases), and enforceStorageClasses (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 legacy CookieConsentBanner test, and useGdprConsent test). tsc --noEmit passes with no errors. No existing files' behavior was changed beyond the one-line hook addition in CookieConsentBanner.tsx.

How to test

pnpm install
pnpm test src/lib/consent src/components/consent
pnpm run type-check

Manually: 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 from localStorage without a page reload.

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.
@drips-wave

drips-wave Bot commented Aug 26, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Thank you for contributing to the project

@RUKAYAT-CODER
RUKAYAT-CODER merged commit a6ef44b into rinafcode:main Aug 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test GDPR Compliance : Storage Classes

2 participants