> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrer les utilisateurs : importer mots de passe et MFA

> Importez vos utilisateurs avec leurs mots de passe, e-mails vérifiés et MFA, depuis un fichier CSV ou JSON, ou par lots, avec Didit.

> [!WARNING]
>
> Authentication est nouveau. Exécutez chaque import d'abord dans une application sandbox, puis importez-le dans votre application de production.

Un import transfère des personnes depuis une autre plateforme d'identité vers Didit Authentication. Leur mot de passe, leur e-mail vérifié, leurs liens de connexion sociale, leur application d'authentification et leurs rôles au sein des organisations sont transférés avec elles. Rien ne change pour elles tant que votre application ne les connecte pas avec Didit.

Les imports utilisent l'API de gestion Authentication de votre application, avec une session de la console qui dispose de `write:authentication` (ou de `read:authentication` pour les lister). Ils n'utilisent jamais un jeton d'utilisateur final ni une clé API de vérification. Dans les exemples, `MANAGEMENT_BASE` correspond à :

```text theme={null}
https://apx.didit.me/auth/v2/organizations/{org_id}/applications/{application_id}/authentication/
```

## Deux façons d'importer

| | Tâche de fichier | Requête par lot |
| - | - | - |
| Endpoint | `POST import-jobs/` (multipart) | `POST imports/` (JSON) |
| Taille | Un fichier CSV ou JSON, jusqu'à 25 Mo et 100 000 personnes | De 1 à 1 000 `rows` par requête |
| Simulation | Toujours exécutée en premier, en arrière-plan | `dry_run: true` par défaut |
| Enregistrement | `POST import-jobs/{id}/start/` | La même requête avec `dry_run: false` |
| Échec partiel | Chaque lot de 1 000 personnes est enregistré séparément ; un import interrompu reprend là où il s'est arrêté | Tout ou rien |

## Importer un fichier

### 1. Envoi

```bash theme={null}
curl --fail-with-body "${MANAGEMENT_BASE}import-jobs/" \
  -H "Authorization: Bearer $CONSOLE_ACCESS_TOKEN" \
  -F file=@users.csv \
  -F passwords=import \
  -F keep_social=true \
  -F keep_verified=true \
  -F keep_mfa=true \
  -F keep_ids=true
```

| Champ | Signification |
| - | - |
| `file` | CSV ou JSON, en UTF-8 (une marque d'ordre des octets est acceptée), jusqu'à 25 Mo. |
| `passwords` | `import` (valeur par défaut) conserve les hachages de mot de passe. `reset` les ignore, afin que chacun définisse un nouveau mot de passe. |
| `keep_social` | Conserve les liens de connexion sociale. |
| `keep_verified` | Conserve `email_verified`. Avec `false`, tous les e-mails sont non vérifiés. |
| `keep_mfa` | Conserve les secrets des applications d'authentification. |
| `keep_ids` | Conserve `external_id`. Avec `false`, `vendor_data` devient l'identifiant d'identité Didit. |
| `firebase_signer_key`, `firebase_salt_separator`, `firebase_rounds`, `firebase_mem_cost` | Les paramètres de hachage de votre projet Firebase. Envoyez les quatre ou aucun. |

Chaque commutateur `keep_*` vaut `true`, sauf si vous envoyez `false`. L'appel renvoie `202` avec une tâche. Une application conserve au maximum 5 fichiers envoyés à la fois.

### 2. Vérifier la simulation

Chaque envoi lance une simulation. Elle contrôle chaque personne sans rien écrire. Interrogez `GET import-jobs/{id}/` :

| Champ | Signification |
| - | - |
| `kind` | `dry_run`, puis `import` une fois que vous l'avez lancé. |
| `status` | `queued`, `running`, puis `passed` ou `failed` pour une simulation, `done` ou `error` pour un import. |
| `processed`, `total` | Progression, en personnes. |
| `result` | Contenu du fichier (voir [résultats](#résultats)). |
| `startable` | `true` lorsque la simulation a réussi et que le fichier n'a pas expiré. |
| `expires_at` | Le fichier est supprimé 24 heures après une simulation qui n'a jamais été importée. |

Une simulation échouée liste ses erreurs. Corrigez le fichier, puis envoyez-le à nouveau.

### 3. Lancer l'import

`POST import-jobs/{id}/start/` importe ce que la simulation a contrôlé et renvoie `202`.

* Les personnes sont importées par lots de 1 000. Chaque lot est enregistré séparément.
* Un seul import s'exécute à la fois par application (sinon `import_running`).
* Un import interrompu reprend après son dernier lot enregistré.
* Les personnes déjà importées sont ignorées, vous pouvez donc relancer le même fichier.
* La personne qui a lancé l'import reçoit un e-mail à la fin ou à l'arrêt de celui-ci.

Le fichier envoyé est stocké chiffré. Il est supprimé à la fin de l'import, lorsque sa simulation échoue, ou 24 heures après une simulation qui n'a jamais été importée.

## Importer par lots

Envoyez jusqu'à 1 000 personnes par requête, avec une `idempotency_key` de 100 caractères maximum.

```json theme={null}
{
  "rows": [
    {
      "email": "user@example.com",
      "external_id": "user-1234",
      "email_verified": true,
      "display_name": "Example User",
      "password_hash": "$2b$12$...",
      "social": [{ "provider": "google", "subject": "1234567890" }],
      "organizations": [{ "slug": "acme", "name": "Acme", "role": "admin" }]
    }
  ],
  "dry_run": true,
  "idempotency_key": "migration-batch-0001"
}
```

* Une simulation renvoie `200` avec le résultat et n'écrit rien.
* Avec `dry_run: false`, un import réussi renvoie `201`. Un lot comportant une erreur renvoie `400` et n'importe aucune personne.
* Répéter une requête enregistrée avec la même clé renvoie son résultat enregistré. La même clé avec un contenu différent renvoie `400 {"idempotency_key": "different_payload"}`.
* Les paramètres Firebase vont dans un objet `firebase` : `{signer_key, salt_separator, rounds, mem_cost}`.
* `GET imports/` liste les lots enregistrés.

## Format du fichier

### JSON

Un tableau de personnes, ou un objet dont `users` contient le tableau.

| Champ | Règle |
| - | - |
| `email` | Obligatoire. |
| `external_id` | 255 caractères maximum. Devient le [`vendor_data`](https://docs.didit.me/fr/entities/vendor-data-linking) de la personne. |
| `email_verified` | `true` ou `false` (aussi `1`/`0`, `yes`/`no`). |
| `display_name` | 200 caractères maximum. |
| `password_hash`, `password_salt`, `password_algorithm` | Voir [mots de passe](#mots-de-passe). |
| `totp_secret` | Secret de l'application d'authentification au format Base32. |
| `created_at`, `last_signed_in_at` | ISO 8601. Une heure sans fuseau est en UTC. |
| `social` | 8 `{provider, subject, tenant_id}` maximum. `provider` vaut `google`, `apple`, `github` ou `microsoft`. `tenant_id` est obligatoire pour Microsoft uniquement. |
| `organizations` | 20 `{slug, name, role}` maximum. `role` vaut `admin`, `member` (valeur par défaut) ou `billing`. |

### CSV

Les mêmes colonnes à valeur unique, plus `google_id`, `apple_id`, `github_id`, `microsoft_id`, `microsoft_tenant_id`, `organization_slug`, `organization_name` et `organization_role`. Une personne membre de plusieurs organisations occupe une ligne par organisation.

```csv theme={null}
email,external_id,email_verified,password_hash,google_id,organization_slug,organization_role
user@example.com,user-1234,true,$2b$12$...,1234567890,acme,admin
```

Les champs ou colonnes inconnus sont des erreurs, jamais ignorés.

### Doublons et comptes existants

* Les lignes ayant le même e-mail forment une seule personne. Une ligne identique est comptée comme doublon. Une ligne comportant davantage de liens ou d'organisations, ou un authentificateur, est fusionnée.
* Deux hachages de mot de passe, secrets d'application d'authentification ou rôles d'organisation différents pour une même personne sont des erreurs. Il en va de même pour un `external_id` ou un identifiant de compte chez un fournisseur revendiqué par deux personnes.
* Si un compte avec cet e-mail existe déjà et qu'un import précédent l'a créé, la personne est ignorée (`already_imported`). Si la personne s'est inscrite d'elle-même, il s'agit d'une erreur `identity_conflict`. Un import ne modifie jamais un compte existant.

## Mots de passe

| Format | Valeurs acceptées |
| - | - |
| bcrypt | `$2a$`, `$2b$` ou `$2y$`, coût de 4 à 13. |
| Argon2id | `$argon2id$v=19$...`. Jusqu'à 64 MiB de mémoire, mémoire multipliée par les passes jusqu'à 256 MiB, jusqu'à 8 voies. |
| Django PBKDF2-SHA256 | `pbkdf2_sha256$...`, de 10 000 à 2 000 000 itérations. |
| Django scrypt | `scrypt$...`, facteur de travail jusqu'à 32,768, taille de bloc de 1 à 8, parallélisme de 1 à 5. |
| Firebase scrypt | `password_algorithm: "firebase_scrypt"`, `password_hash` et `password_salt` en base64 pour chaque personne, ainsi que le `signer_key`, le `salt_separator`, les `rounds` (de 1 à 8) et le `mem_cost` (de 1 à 14) de votre projet, une seule fois par importation. |

Les paramètres Firebase sont stockés chiffrés, et uniquement pour cette application. Tout autre format, ou un coût supérieur à ces limites, impose à la personne de définir un nouveau mot de passe. Après la première connexion réussie d'une personne, Didit enregistre à nouveau son mot de passe dans son propre format.

### Personnes sans mot de passe utilisable

Lorsqu'une personne n'a pas de hachage, ou un hachage non pris en charge, une tentative de connexion par mot de passe reçoit la réponse habituelle de mot de passe incorrect. Didit lui envoie aussi un e-mail contenant un lien pour définir un nouveau mot de passe (valable 30 minutes, envoyé au plus une fois par jour). Cela ne se produit que si son e-mail est vérifié, si le compte n'est pas bloqué et si la connexion par mot de passe est activée.

Dans le résultat, `reset_required` compte les personnes sans mot de passe utilisable. `reset_emails` compte, parmi elles, celles dont l'e-mail est vérifié : seules celles-ci peuvent être jointes de cette manière. Les personnes dont l'e-mail n'est pas vérifié ne peuvent pas se connecter avec un mot de passe. Elles peuvent toutefois se connecter avec un code envoyé par e-mail, si vous activez cette méthode.

## Liens sociaux, authentificateurs et organisations

* Les **liens sociaux** sont conservés pour les fournisseurs que votre application a déjà configurés, sous son ID client actuel. Une connexion ultérieure ne nécessite alors aucune preuve de l'e-mail. Les identifiants (sujets) Google et GitHub sont globaux. Les identifiants Apple dépendent de l'équipe de développement, et ceux de Microsoft de l'enregistrement de l'application : ces liens ne correspondent donc que si votre ancienne plateforme utilisait la même équipe ou le même enregistrement. Les liens vers des fournisseurs que vous n'avez pas configurés sont comptés comme `social_unavailable` et ne sont pas importés.
* Les **applications d'authentification** (secrets TOTP : SHA-1, 6 chiffres, 30 secondes) sont stockées chiffrées. Les personnes importées doivent les utiliser comme second facteur. Elles ne reçoivent aucun code de récupération.
* Les **organisations** qui n'existent pas encore sont créées à partir de leur slug.
* Les **clés d'accès** ne peuvent pas être importées. Elles appartiennent à votre ancien domaine de connexion ; les personnes en ajoutent donc une nouvelle après s'être connectées avec Didit.

## Résultats

Chaque essai à blanc, lot et tâche renvoie le même `result` :

| Champ | Signification |
| - | - |
| `rows`, `people_total` | Lignes lues et personnes distinctes. |
| `ready`, `already_imported`, `duplicates`, `merged`, `skipped` | Ce qui arrive à chaque personne. |
| `passwords` | Nombre par format : `bcrypt`, `argon2id`, `firebase_scrypt`, `pbkdf2_sha256`, `scrypt`. |
| `reset_required`, `reset_emails`, `unsupported_hashes` | Personnes qui doivent définir un nouveau mot de passe. |
| `verified_emails` | Personnes importées avec un e-mail vérifié. |
| `social_links`, `social_unavailable` | Liens conservés par fournisseur, et liens ignorés. |
| `authenticator_apps` | Personnes disposant d'une application d'authentification importée. |
| `organizations` | `count`, `members` et jusqu'à cinq `names`. |
| `error_count`, `errors` | Jusqu'à 100 erreurs, chacune avec un `row` à partir de 0 (ligne CSV `row + 2`), un `field` et un code `error`. |

Les réponses ne contiennent jamais de hachages de mots de passe, de sels, de secrets d'authentificateur ni de valeurs de champs.

| Erreur | Signification |
| - | - |
| `file_too_large`, `too_many_rows`, `empty_file` | Plus de 25 Mo, plus de 100 000 personnes, ou fichier vide. |
| `invalid_encoding`, `invalid_json`, `invalid_csv` | Le fichier ne peut pas être lu. |
| `unknown_column`, `missing_column`, `duplicate_column`, `unknown_field` | Le fichier ne correspond pas au format. |
| `required`, `invalid_field`, `invalid_row` | Une valeur est manquante ou invalide. |
| `identity_conflict` | Un compte associé à cet e-mail s'est inscrit de son côté. |
| `social_link_conflict`, `password_conflict`, `authenticator_conflict`, `role_conflict` | Deux lignes pour une même personne ne concordent pas. |
| `too_many_uploads`, `import_running`, `not_startable`, `file_expired` | La tâche ne peut pas démarrer maintenant. |

## Avant de basculer

Testez dans votre application sandbox :

* la connexion avec chaque format de mot de passe que vous importez,
* l'e-mail de définition d'un nouveau mot de passe pour une personne sans hachage utilisable,
* la connexion via les réseaux sociaux et les codes des applications d'authentification pour les personnes importées,
* l'exécution deux fois du même fichier.

Vérifiez ensuite que le `vendor_data` de chaque personne correspond à la valeur que vous utilisez pour ses [vérifications et transactions](https://docs.didit.me/fr/authentication/overview.md#garder-une-même-personne-entre-les-produits).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.