# Questions — migration des anciens comptes et `POST /auth/attach`

> **À l'attention de l'équipe backend** · 18/09/2026 · application mobile Amana
>
> **Objet.** Les maquettes du parcours Connexion prévoient deux branches pour les
> anciens comptes. Nous savons que `POST /auth/attach` existe, mais nous ne
> connaissons ni le modèle de données issu de la migration, ni le contrat réel de
> cet endpoint. Nous préférons poser les questions plutôt que de construire les
> écrans sur une supposition et de devoir les refaire.
>
> **Ce document ne contient aucune proposition de votre part** : tout ce qui suit
> est soit un fait observé sur la pré-production, soit une question. Notre propre
> hypothèse de travail est isolée en **§7**, uniquement pour vous donner quelque
> chose de concret à corriger.

---

## 1. État des lieux côté mobile

| Parcours de connexion | État |
|---|---|
| Nouvel utilisateur (inscription + connexion) | ✅ implémenté, vérifié en live |
| Mot de passe oublié (email → OTP → nouveau mot de passe) | ✅ implémenté, vérifié en live |
| Défis `419` (mot de passe à changer) et `420` (MFA) | ✅ implémentés |
| Connexion par empreinte digitale | ✅ implémentée (voir la réserve en §9) |
| **Ancien compte Santé + compte Assurance à rattacher** | 🟡 écrans faits, **déclencheur manquant** — §3 à §5 |
| **Ancien compte Assurance seulement** | 🟡 écran fait, **parcours à confirmer** — §6 |

**Précision sur les deux branches en attente.** Depuis la première rédaction de ce
document, nous avons construit leurs écrans d'après les maquettes : l'écran de
transition, le formulaire de saisie du second compte, l'écran de confirmation, et
l'écran « ancien compte Amana ». Ils sont traduits (FR/AR) et couverts par des
tests.

Ils restent cependant **inaccessibles dans l'application** : aucun chemin n'y
mène, parce que rien dans la réponse de `POST /auth/login` ne nous dit quand les
afficher. C'est l'objet de **Q8** (et de **Q9** pour la seconde branche). Nous
n'avons toujours pas appelé `POST /auth/attach`, ni en développement ni en
recette.

Autrement dit, ce ne sont pas les écrans qui nous bloquent, mais **le déclencheur
et le contrat**. Vos réponses ne nous feront donc rien jeter — elles brancheront
un parcours déjà prêt, ou corrigeront nos suppositions avant la première mise en
service.

## 2. Ce que nous observons réellement

Vérifié le 18/09/2026 sur `test-chahinaz@amana.dz` (`GENERAL_USER`, pré-production).

**Réponse réelle de `POST /auth/login` :**

```jsonc
{
  "nom": "test", "prenom": "test", "email": "test-chahinaz@amana.dz",
  "user_id": 3568, "personne_id": "7012",
  "checkuser": false,       // présent à chaque connexion — usage inconnu de notre côté
  "altern_users": null,     // jamais observé peuplé
  "jwt": "…", "refresh": "…"
}
```

**Ce que la spécification OpenAPI v4 déclare pour l'endpoint :**

```
POST /auth/attach
  operationId : newUserFromOld
  security    : bearerAuth
  requestBody : application/json → LoginRequest { username, password }
  réponses    : 200 "user found"      → LoginDto
                401 "token non valid" → ServerError
                403 "user not found"  → WithMessageExtra
  summary / description : absents
```

Nous n'avons **pas** appelé cet endpoint : sans comprendre ce qu'il modifie en
base, nous ne voulions pas risquer de rattacher des comptes par erreur sur la
pré-production.

## 3. Le modèle issu de la migration

Ces questions sont les plus structurantes : les suivantes en découlent.

**Q1.** Après la migration des utilisateurs Santé vers la base unifiée, **qu'est-ce
qu'un « compte unifié » concrètement ?** Une seule ligne utilisateur par personne
physique, ou une ligne pivot qui référence les anciens comptes ?

**Q2.** Nous voyons circuler plusieurs identifiants : `user_id`, `personne_id`,
`adherentSanteId`, `userSanteId`, `contactInfoId`, `uid`. **Lequel identifie la
personne** de façon stable après un rattachement, et lesquels peuvent changer ?

**Q3.** **Que fait `POST /auth/attach`, décrit avec vos mots ?** Qu'est-ce qui est
lié à quoi, et qu'est-ce qui change en base une fois l'appel réussi ?

**Q4.** **Cet endpoint est-il déjà utilisé en production par un autre client** (le
portail web, un back-office) ? Si oui, une implémentation de référence à regarder
nous éviterait de vous solliciter davantage.

## 4. Le contrat de l'appel

**Q5.** **Que contient le corps, et sous quelle session l'appel est-il émis ?**
L'endpoint est marqué `bearerAuth` et son corps est un `LoginRequest`
(`{ username, password }`) : cela suggère qu'on s'authentifie avec une session et
qu'on transmet les identifiants de l'**autre** compte dans le corps. Est-ce bien
le principe ? Si oui, **quelle session doit porter l'appel** — celle du compte
Santé, celle du compte Assurance ?

**Q6.** **Que renvoie exactement un rattachement réussi ?** La spec annonce un
`LoginDto`, donc un objet contenant `jwt` et `refresh`.

- Sont-ce de **nouveaux jetons** que l'application doit substituer à ceux qu'elle
  a stockés, ou un simple écho informatif ?
- Le `personne_id` renvoyé peut-il **différer** de celui d'avant l'appel ? Si oui
  nous devons vider tout le cache de données de l'utilisateur au même instant,
  sans quoi il verrait les contrats de l'autre profil.

**Q7.** **Quels sont les cas d'échec et comment les distinguer ?** La maquette
prévoit un message dédié (« Nous n'avons pas pu reconnaître votre compte »), donc
il nous faut séparer :

| Situation | Code et corps attendus ? |
|---|---|
| Identifiants de l'autre compte erronés | ? |
| Compte inexistant | `403` + `WithMessageExtra` ? |
| Compte déjà rattaché à **un autre** compte unifié | ? |
| Compte déjà rattaché **à celui-ci** (rejeu du même appel) | ? |

Le dernier cas nous intéresse particulièrement : **l'opération est-elle
idempotente ?** Sur mobile, un double appui ou un rejeu réseau est courant, et il
ne doit pas produire d'état incohérent.

## 5. Le déclenchement côté application

**Q8.** L'application doit savoir **quand** proposer le rattachement. Aujourd'hui
elle n'a aucun moyen fiable de le déterminer.

- Le champ **`checkuser`** sert-il à cela ? Que vaut-il pour un ancien compte Santé
  migré, au premier login puis aux suivants ? (Sur notre compte de test : `false`.)
- Le champ **`altern_users`** contient-il les comptes **déjà rattachés** — ce qui
  nous permettrait de ne plus reproposer l'opération ? **Quelle est la forme d'un
  élément** (identifiant, type de compte, libellé) ?
- Si ni l'un ni l'autre ne convient : **quel champ devons-nous regarder**, ou
  faut-il en ajouter un ? Un discriminant explicite dans la réponse de
  `/auth/login` nous éviterait toute heuristique fragile.

> Sans réponse à Q8, l'écran de transition s'afficherait soit jamais, soit à tous
> les utilisateurs, y compris aux nouveaux inscrits qui n'ont rien à rattacher.

## 6. Le parcours « ancien compte Assurance seulement »

**Q9.** Un ancien utilisateur qui possédait **uniquement** un compte Assurance
(sans compte Santé, donc non concerné par la migration) :

- peut-il **se connecter aujourd'hui** à l'application avec ses identifiants
  historiques via `POST /auth/login` ? Si oui, tout va bien et cette branche
  n'existe pas.
- si non, **que répond `POST /auth/login`** dans son cas — quel code, quel corps ?
  C'est cette réponse qui doit déclencher l'écran « Essayez-vous de vous connecter
  à un ancien compte Amana ? ».
- et quel est alors le parcours prévu ? `/auth/attach` exige une session
  (`bearerAuth`), or cet utilisateur n'en a aucune : faut-il qu'il crée d'abord un
  compte sur l'application, puis rattache ?

## 7. Notre hypothèse de travail — à corriger

> ⚠️ **Ceci n'est pas une spécification et ne vient pas de vous.** C'est ce que
> nous construirions si nous n'avions aucune réponse, reconstitué à partir des
> maquettes et de la spec OpenAPI. Nous le mettons par écrit uniquement pour vous
> donner quelque chose de concret à corriger — il est probablement faux sur
> plusieurs points.

1. L'utilisateur se connecte avec ses identifiants **Santé** → `POST /auth/login`
   → l'application obtient une session.
2. Un champ de la réponse (nous supposons `checkuser`) indique qu'il s'agit d'un
   ancien compte Santé migré → l'app affiche l'écran de transition « Vous êtes
   maintenant connecté à votre compte d'assurance santé · Avez-vous un autre
   compte d'assurance Amana ? ».
3. S'il répond **Non** → activation de l'empreinte → accueil.
4. S'il répond **Oui** → écran de saisie des identifiants **Assurance** → un seul
   appel `POST /auth/attach`, **portant le JWT Santé** dans l'en-tête
   `Authorization` et les **identifiants Assurance dans le corps**.
5. En cas de succès, l'app remplace ses jetons par ceux du `LoginDto` renvoyé,
   vide son cache, affiche « Vos comptes sont maintenant liés ! », puis l'écran
   d'empreinte et l'accueil.

**Le point sur lequel nous aimerions le plus être détrompés (ou confirmés) :** à
l'étape 4, nous supposons qu'il n'y a **pas** de connexion préalable avec les
identifiants Assurance. Si l'application devait d'abord appeler `POST /auth/login`
avec ceux-ci, cela **remplacerait la session Santé** au moment précis où il faut
appeler `/auth/attach` — l'app serait alors connectée au mauvais compte. S'il
existe un mécanisme prévu pour ce cas (un endpoint de validation d'identifiants
qui n'ouvre pas de session, un jeton de courte durée), c'est exactement ce qu'il
nous manque.

## 8. Ce qui nous débloquerait le plus vite

Par ordre d'utilité :

1. **Un `curl` complet d'un rattachement réussi** — requête et réponse, jetons
   masqués. Il répond à Q5, Q6 et à une partie de Q7 d'un seul coup, et vaut mieux
   que n'importe quelle description.
2. **Un compte de test sur la pré-production** possédant un ancien compte Santé
   **et** un ancien compte Assurance non encore rattachés, avec les deux jeux
   d'identifiants. Sans lui nous ne pouvons ni développer ni recetter ces écrans.
3. **La réponse à Q8** (le déclencheur), dont dépend tout l'enchaînement.

## 9. Deux points connexes

**Le CIN collecté à l'inscription n'a nulle part où aller.** L'écran d'inscription
demande un numéro d'identification national, mais le corps de `POST /users`
(`ChargeUserFromDigitalParameter`) n'expose aucun champ pour le recevoir : la
valeur est actuellement perdue. **Devait-elle servir à rapprocher un nouvel
inscrit de son ancien dossier Amana ?** Si oui, quel champ doit la porter ; sinon,
confirmez-vous que nous pouvons retirer ce champ de l'écran ?

**`POST /auth/refresh` répond `500`, et cela touche ce parcours.** Vérifié le 18/09
à 19:03 avec un jeton de rafraîchissement valide, émis 2 minutes plus tôt et
expirant dans 8 heures. Le nom de champ est bien celui attendu — un corps
`{ "refresh": … }` répond `400 « ne doit pas être vide »`, ce qui confirme que
`refreshToken` est correct. Comme le jeton d'accès vit **30 minutes**, tout
utilisateur inactif au-delà est **déconnecté** au lieu d'être renouvelé, et la
connexion par empreinte digitale échoue pour la même raison — elle ne fonctionne
aujourd'hui que dans la demi-heure suivant une connexion par mot de passe. Détail
et traces dans `docs/rapports-internes/board-reporting/audit-api-reel-vs-specification.md` §2.1.
