feat(legal): registre des sous-traitants et inventaire reel des cookies
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
This commit is contained in:
parent
94a681601f
commit
5504f9c1ce
113
apps/frontend/src/lib/legal/processing.ts
Normal file
113
apps/frontend/src/lib/legal/processing.ts
Normal file
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Registre des traitements — source unique.
|
||||
*
|
||||
* ⚠️ Contenu juridique. Ce fichier liste les destinataires des données et ce
|
||||
* que le site dépose sur l'appareil du visiteur. La politique de
|
||||
* confidentialité publique, la politique de cookies et la page de conformité
|
||||
* de l'administration le lisent toutes les trois : un sous-traitant ajouté ici
|
||||
* apparaît partout, et il devient impossible de mettre à jour l'un en oubliant
|
||||
* les autres. Il tient lieu de registre de l'article 30 côté produit.
|
||||
*
|
||||
* Seuls les faits structurels vivent ici — noms, nature, encadrement du
|
||||
* transfert. Les descriptions sont dans les fichiers de messages, sinon la
|
||||
* moitié de la page resterait en français pour un lecteur anglophone.
|
||||
*
|
||||
* Les durées de conservation, elles, vivent côté serveur
|
||||
* (`domain/services/data-retention`) : c'est le serveur qui les applique.
|
||||
*/
|
||||
|
||||
/** Fondement d'un transfert hors Union européenne (chap. V du RGPD). */
|
||||
export type TransferBasis =
|
||||
| 'eu' // Aucune sortie de l'Union.
|
||||
| 'adequacy' // Décision d'adéquation de la Commission.
|
||||
| 'scc'; // Clauses contractuelles types.
|
||||
|
||||
export interface Subprocessor {
|
||||
/** Clé de traduction sous `marketing.privacy.processors`. */
|
||||
key: string;
|
||||
/** Nom commercial, identique dans toutes les langues. */
|
||||
name: string;
|
||||
transferBasis: TransferBasis;
|
||||
/** Vrai si le prestataire n'intervient que sur une fonctionnalité précise. */
|
||||
conditional: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chaque entrée correspond à un service réellement branché, vérifié dans le
|
||||
* code — pas une liste d'intentions.
|
||||
*/
|
||||
export const SUBPROCESSORS: readonly Subprocessor[] = [
|
||||
{ key: 'stripe', name: 'Stripe', transferBasis: 'scc', conditional: false },
|
||||
{ key: 'openai', name: 'OpenAI', transferBasis: 'scc', conditional: true },
|
||||
{ key: 'sentry', name: 'Sentry', transferBasis: 'scc', conditional: false },
|
||||
{ key: 'pappers', name: 'Pappers', transferBasis: 'eu', conditional: true },
|
||||
{ key: 'oauth', name: 'Google, Microsoft', transferBasis: 'scc', conditional: true },
|
||||
];
|
||||
|
||||
export interface StoredItem {
|
||||
/** Nom technique, tel qu'il apparaît dans le navigateur. */
|
||||
name: string;
|
||||
/** Cookie déposé par le serveur, ou valeur écrite par la page. */
|
||||
kind: 'cookie' | 'localStorage';
|
||||
}
|
||||
|
||||
/**
|
||||
* Ce que le site dépose réellement.
|
||||
*
|
||||
* L'inventaire précédent annonçait Google Analytics (`_ga`, `_gid`, `_gat`),
|
||||
* Facebook Pixel (`_fbp`), LinkedIn (`li_fat_id`) et HubSpot (`hubspotutk`).
|
||||
* Aucun de ces scripts n'est chargé par l'application : décrire des traceurs
|
||||
* qui n'existent pas est aussi inexact que d'en dissimuler, et rendait sans
|
||||
* objet le consentement demandé pour eux.
|
||||
*/
|
||||
export const ESSENTIAL_STORAGE: readonly StoredItem[] = [
|
||||
{ name: 'accessToken', kind: 'cookie' },
|
||||
{ name: 'refreshToken', kind: 'cookie' },
|
||||
{ name: 'xpeditis_session', kind: 'cookie' },
|
||||
{ name: 'cookieConsent', kind: 'localStorage' },
|
||||
];
|
||||
|
||||
export const FUNCTIONAL_STORAGE: readonly StoredItem[] = [{ name: 'NEXT_LOCALE', kind: 'cookie' }];
|
||||
|
||||
/**
|
||||
* Responsable du traitement (art. 13.1.a).
|
||||
*
|
||||
* Les champs à `null` sont obligatoires et manquants. La page publique ne
|
||||
* publie que ce qui est renseigné, et la console d'administration les signale :
|
||||
* mieux vaut une mention absente, visible et corrigeable, qu'une mention
|
||||
* inventée.
|
||||
*/
|
||||
export interface ControllerIdentity {
|
||||
tradingName: string;
|
||||
legalName: string | null;
|
||||
registrationNumber: string | null;
|
||||
address: string | null;
|
||||
privacyContact: string;
|
||||
/** Délégué à la protection des données, si un DPO a été désigné. */
|
||||
dpo: string | null;
|
||||
}
|
||||
|
||||
export const CONTROLLER: ControllerIdentity = {
|
||||
tradingName: 'Xpeditis',
|
||||
legalName: null,
|
||||
registrationNumber: null,
|
||||
address: null,
|
||||
privacyContact: 'privacy@xpeditis.com',
|
||||
dpo: null,
|
||||
};
|
||||
|
||||
/** Autorité de contrôle compétente, à mentionner au titre de l'art. 13.2.d. */
|
||||
export const SUPERVISORY_AUTHORITY = {
|
||||
name: 'Commission nationale de l’informatique et des libertés (CNIL)',
|
||||
url: 'https://www.cnil.fr/fr/plaintes',
|
||||
} as const;
|
||||
|
||||
/** Mentions obligatoires encore absentes de l'identité du responsable. */
|
||||
export function missingControllerFields(controller: ControllerIdentity = CONTROLLER): string[] {
|
||||
const required: [keyof ControllerIdentity, string][] = [
|
||||
['legalName', 'legalName'],
|
||||
['registrationNumber', 'registrationNumber'],
|
||||
['address', 'address'],
|
||||
];
|
||||
return required.filter(([field]) => !controller[field]).map(([, key]) => key);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user