Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
114 lines
4.4 KiB
TypeScript
114 lines
4.4 KiB
TypeScript
/**
|
||
* 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);
|
||
}
|