My App

RGPD & conformité

Sous-traitants, retention, droit à l'oubli, PII en logs, cookies, DPO

HOAIY est sous-traitant pour les données guest (responsable = l'hôtel), responsable pour les données staff. Toutes les infra UE (Hostinger Larnaca, Signoz self-host). Pas de GAFAM US pour les données personnelles sauf Azure OpenAI (traitement AI non-stockage).

Statut RGPD de HOAIY

Pour les données guests

  • Responsable de traitement : l'hôtel (utilise Bell pour gérer ses clients)
  • Sous-traitant : HOAIY (fournit la plateforme)
  • Convention de sous-traitance (DPA) signée entre HOAIY et chaque hôtel à l'onboarding

Pour les données staff (users HOAIY + staff des hôtels)

  • Responsable de traitement : HOAIY (c'est son produit, ses comptes)
  • Politique de confidentialité publique sur bell-docs.hoaiy.com/legal/privacy (ou hoaiy.com/legal)

Sous-traitants identifiés

Tous listés dans le DPA signé avec les hôtels. Hébergement tout UE ou équivalent.

Sous-traitantRôleLocalisationTransfert hors UE ?
Hostinger VPSHébergement apps + DB + Redis + Signoz + RustFSLarnaca, Chypre (UE)Non
Cloudflare R2Backups DB + storage (S3-compatible)Multiple (dépend de la région)Non (bucket EU)
MewsPMS (sous-traitant de l'hôtel, pas de HOAIY)Amsterdam, NLNon
StripePaiementsDublin (siège européen)Non (branche européenne)
Azure OpenAILLM conciergeWest Europe region (Irlande ou NL)Non si config correcte
Google Workspace SMTPEnvoi d'emails transactionnelsUS principalement⚠️ Transfert US
ReacherValidation email (self-host)Chez nousNon
SignozMonitoring (self-host)Chez nousNon

Transfert Google Workspace

Gmail utilise des Standard Contractual Clauses (SCCs) + DPF (Data Privacy Framework) de Google. C'est juridiquement OK pour l'instant, mais on envisage de migrer vers un SMTP européen (Brevo, Postmark EU, ScalewayMail) au cas où.

Dans le DPA, mention explicite : "les emails transactionnels transitent par Google Workspace US, sous SCCs + DPF".

Données collectées

Pour les guests

TypeFinalitéBase légaleRetention
Identité (nom, prénom)Check-in, adresse de livraison commandesExécution du contrat hôtelierDurée séjour + 3 ans (comptable)
Email, téléphoneCommunication check-in, notificationsExécution du contratDurée séjour + 3 ans
Date de naissanceVérification majorité (bar, spa)Obligation légaleDurée séjour + 3 ans
Moyen de paiementPaiement commandesExécution du contratStripe gère, on stocke juste paymentIntentId
Préférences (allergies, VIP)Personnalisation du serviceIntérêt légitimeDurée séjour
Conversations AI + staffSupport + amélioration IAIntérêt légitime2 ans
Check-in status + timestampsAnalytics + auditIntérêt légitime3 ans
Cookies techniques (session)Fonctionnement appStrictement nécessaire7 jours (session)

Pour les staff

TypeFinalitéBase légaleRetention
Identité, emailAccès au dashboardContrat de travail (avec l'hôtel) / Bell account (HOAIY)Jusqu'à révocation + 1 an
Logs d'activitéAudit, supportIntérêt légitime1 an
Session + IPSécuritéIntérêt légitime1 an

Droits des personnes

Implémentation dans Bell :

Droit d'accès

  • Guest : bouton "Télécharger mes données" dans la PWA (/settings/my-data) → génère un JSON avec tout ce qu'on a sur lui (profil, bookings, conversations, messages)
  • Staff : export équivalent dans /dashboard/settings/my-data

Technique : procedure Elysia /users/export-my-data qui collecte + JSON + link de téléchargement signé (R2, expires 1h).

Droit de rectification

  • Guest : édite son profil dans /settings/profile (PWA). Pour les champs read-only (email, nom), il doit contacter l'hôtel.
  • Staff : édite dans /dashboard/settings/profile

Droit à l'oubli

  • Guest : bouton "Supprimer mon compte" → soft delete user.deletedAt + guest.deletedAt, anonymization des data conservées pour obligations légales (facturation)
  • Après la retention légale, hard delete physique (cron @elysiajs/cron qui purge les soft-deleted de + de 3 ans)

Données anonymisées mais conservées (obligation comptable 3 ans) :

  • Total des factures, dates, TVA
  • Nom hôtel, nom guest → hash
// cron quotidien
async function anonymizeExpired() {
  const guests = await db.select().from(guest).where(
    and(
      isNotNull(guest.deletedAt),
      lt(guest.deletedAt, subDays(new Date(), 30)),        // 30 jours après soft delete → anonymize
    ),
  );
  for (const g of guests) {
    await db.update(guest).set({
      firstName: "Anonymisé",
      lastName: hashShort(g.id),
      email: `deleted-${hashShort(g.id)}@anonymized.local`,
      phone: null,
      notes: null,
    }).where(eq(guest.id, g.id));
  }
}

async function hardDeleteExpired() {
  await db.delete(guest).where(
    and(
      isNotNull(guest.deletedAt),
      lt(guest.deletedAt, subYears(new Date(), 3)),        // 3 ans → hard delete
    ),
  );
}

Droit d'opposition / portabilité

  • Opposition : opt-out des emails marketing via lien dans chaque email + /settings/notifications
  • Portabilité : JSON export (voir droit d'accès)

Délai de réponse

1 mois à partir de la demande (allongeable de 2 mois si complexe). Trackée dans un ticket interne + alerting Signoz si un ticket "rgpd" dépasse 25 jours.

Logs sans PII

Voir Monitoring.

  • Jamais d'email complet dans les logs — toujours hashé (hashEmail(email))
  • Jamais de téléphone, adresse, numéro de carte
  • Tokens Better Auth jamais loggés, même tronqués
  • Linter custom no-pii-in-logs (à écrire) : détecte logger.*({ email: ... }) au commit

Cookies

Catégories

CatégorieCookieBase légaleConsentement requis
Strictement nécessairebetter-auth.session_tokenExécution du contratNon
Préférences(aucun pour l'instant)Oui si ajoutés
AnalyticsAucun (on utilise Signoz côté serveur, pas de pixel tracking)Oui si ajoutés
MarketingAucunOui si ajoutés

Bandeau cookies

Au MVP : pas de bandeau requis puisqu'on n'a que des cookies strictement nécessaires. Si on ajoute de l'analytics (Plausible self-host envisagé, sans PII), on pourrait toujours éviter le bandeau car Plausible ne track pas individuellement.

Si un jour on veut du marketing/retargeting : bandeau obligatoire avec consentement granulaire (pas juste Accept All).

Chiffrement

  • En transit : TLS 1.3 partout (Let's Encrypt via Traefik)
  • Au repos :
    • Postgres : chiffrement disque VPS (Hostinger fournit)
    • Backups R2 : server-side encryption (R2 chiffre par défaut)
    • integration.credentials chiffrés en AES-256 au niveau app avec ENCRYPTION_KEY (32 bytes, env var, différent par env)

Rotation ENCRYPTION_KEY

Procédure rare (tous les 2 ans ou sur incident) :

  1. Générer nouvelle key
  2. Décrypter tous les integration.credentials avec ancienne, ré-encrypter avec nouvelle
  3. Deployer nouveau key
  4. Runbook détaillé dans operations/runbooks/rotate-encryption-key.mdx (à écrire)

Accès aux données

Principe du moindre privilège :

DonnéeQui y accède
Guest profile d'un hôtelStaff+ de cet hôtel, admin HOAIY
Guest passwordPersonne (hashé Better Auth, impossible à lire)
Guest paymentStripe (on stocke juste paymentIntentId + status)
Logs SignozAdmin HOAIY
Backups R2Admin HOAIY (accès IAM limité)
Credentials Mews (chiffrés)Admin HOAIY (via /admin/system/integrations)
AI conversationsStaff+ de l'hôtel, admin HOAIY

DPO (Data Protection Officer)

Nommé par HOAIY. Email dpo@hoaiy.com. Contact listé dans la politique de confidentialité.

Responsabilités :

  • Tenir le registre des traitements
  • Recevoir les demandes de droits des personnes
  • Auditer annuellement l'infra + les sous-traitants
  • Être le contact de la CNIL en cas de contrôle ou breach

Breach notification

En cas de fuite de données :

  1. Détection (Signoz alerte ou signalement externe)
  2. Confinement immédiat (isoler le service compromis)
  3. Évaluer l'impact (combien de personnes, quelles données)
  4. Si risque élevé → notification CNIL sous 72h + personnes concernées
  5. Runbook incident dans operations/runbooks/data-breach.mdx (à écrire)

Audit log

Toute action sensible (édition d'un guest, suppression, changement de role staff, accès aux credentials integration) est loggée dans activity_log avec :

  • Qui : userId
  • Quoi : activity_type
  • Quand : timestamp
  • Sur quoi : entity_type + entity_id
  • Depuis où : ip_address, user_agent

Visible dans /admin/events (admin HOAIY uniquement).

Consentements marketing

Les guests ont une checkbox explicite sur la page check-in paiement :

☐ Je souhaite recevoir des offres exclusives et promotions par email.

Stocké dans guest.marketing_consent. Si non coché → pas d'email commercial. Si décoché plus tard → on stoppe.

Minimisation

On collecte le strict nécessaire :

  • Pas de date de naissance si pas de service âge-restricted
  • Pas d'adresse si pas de service delivery hors hôtel
  • Pas de photo de pièce d'identité (scan passeport) — sauf si obligation légale (fiche de police France) et là c'est l'hôtel qui gère via son PMS, pas Bell

Lien

On this page