> ## 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.

# Impor Pengguna: Migrasi Kata Sandi dan MFA

> Impor pengguna ke Didit beserta kata sandi, email terverifikasi, tautan sosial, aplikasi autentikator, dan peran organisasi dari CSV, JSON, atau batch.

> [!WARNING]
>
> Autentikasi adalah fitur baru. Jalankan setiap impor di aplikasi sandbox terlebih dahulu, lalu impor ke aplikasi produksi Anda.

Impor memindahkan orang dari platform identitas lain ke Didit Authentication. Kata sandi, email terverifikasi, tautan login sosial, aplikasi autentikator, dan peran organisasi mereka ikut berpindah. Tidak ada yang berubah bagi mereka sampai aplikasi Anda mengautentikasi mereka dengan Didit.

Impor menggunakan API manajemen Autentikasi untuk aplikasi Anda dengan sesi konsol yang memiliki `write:authentication` (atau `read:authentication` untuk menampilkan daftar). Impor tidak pernah menggunakan token pengguna akhir atau kunci API verifikasi. Dalam contoh, `MANAGEMENT_BASE` adalah:

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

## Dua cara impor

| | Tugas file | Permintaan batch |
| - | - | - |
| Endpoint | `POST import-jobs/` (multipart) | `POST imports/` (JSON) |
| Ukuran | Satu file CSV atau JSON, hingga 25 MB dan 100.000 orang | 1 sampai 1.000 `rows` per permintaan |
| Dry run | Selalu dijalankan lebih dahulu, di latar belakang | `dry_run: true` secara default |
| Commit | `POST import-jobs/{id}/start/` | Permintaan yang sama dengan `dry_run: false` |
| Kegagalan sebagian | Setiap batch 1.000 di-commit secara terpisah; impor yang terputus dilanjutkan kembali | Semua atau tidak sama sekali |

## Impor file

### 1. Unggah

```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
```

| Kolom | Arti |
| - | - |
| `file` | CSV atau JSON, UTF-8 (byte order mark diizinkan), hingga 25 MB. |
| `passwords` | `import` (default) menyimpan hash kata sandi. `reset` mengabaikannya, sehingga setiap orang menetapkan kata sandi baru. |
| `keep_social` | Menyimpan tautan login sosial. |
| `keep_verified` | Menyimpan `email_verified`. Dengan `false`, setiap email tidak terverifikasi. |
| `keep_mfa` | Menyimpan rahasia aplikasi autentikator. |
| `keep_ids` | Menyimpan `external_id`. Dengan `false`, `vendor_data` menjadi ID identitas Didit. |
| `firebase_signer_key`, `firebase_salt_separator`, `firebase_rounds`, `firebase_mem_cost` | Parameter hash dari proyek Firebase Anda. Kirim keempatnya atau tidak sama sekali. |

Setiap sakelar `keep_*` bernilai `true` kecuali Anda mengirim `false`. Panggilan ini mengembalikan `202` dengan sebuah tugas. Sebuah aplikasi menyimpan paling banyak 5 file yang diunggah pada satu waktu.

### 2. Periksa dry run

Setiap unggahan langsung memulai dry run. Dry run memeriksa setiap orang dan tidak menulis apa pun. Lakukan polling ke `GET import-jobs/{id}/`:

| Kolom | Arti |
| - | - |
| `kind` | `dry_run`, lalu `import` setelah Anda memulainya. |
| `status` | `queued`, `running`, lalu `passed` atau `failed` untuk dry run, `done` atau `error` untuk impor. |
| `processed`, `total` | Kemajuan, dalam jumlah orang. |
| `result` | Isi file (lihat [hasil](#hasil)). |
| `startable` | `true` ketika dry run lolos dan file belum kedaluwarsa. |
| `expires_at` | File dihapus 24 jam setelah dry run jika file tersebut tidak pernah diimpor. |

Dry run yang gagal mencantumkan kesalahannya. Perbaiki file lalu unggah ulang.

### 3. Mulai impor

`POST import-jobs/{id}/start/` mengimpor apa yang sudah diperiksa oleh dry run dan mengembalikan `202`.

* Orang diimpor dalam batch 1.000. Setiap batch di-commit secara terpisah.
* Hanya satu impor yang berjalan per aplikasi pada satu waktu (jika tidak, `import_running`).
* Impor yang terputus dilanjutkan setelah batch terakhir yang di-commit.
* Orang yang sudah diimpor akan dilewati, sehingga Anda dapat menjalankan file yang sama lagi.
* Orang yang memulai impor menerima email ketika impor selesai atau berhenti.

File yang diunggah disimpan dalam keadaan terenkripsi. File dihapus ketika impor berakhir, ketika dry run-nya gagal, atau 24 jam setelah dry run jika file tersebut tidak pernah diimpor.

## Impor dalam batch

Kirim hingga 1.000 orang per permintaan dengan `idempotency_key` hingga 100 karakter.

```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"
}
```

* Dry run mengembalikan `200` dengan hasilnya dan tidak menulis apa pun.
* Dengan `dry_run: false`, impor yang berhasil mengembalikan `201`. Batch dengan kesalahan apa pun mengembalikan `400` dan tidak mengimpor siapa pun.
* Mengulangi permintaan yang sudah di-commit dengan kunci yang sama mengembalikan hasil yang tercatat. Kunci yang sama dengan isi berbeda mengembalikan `400 {"idempotency_key": "different_payload"}`.
* Parameter Firebase dimasukkan ke dalam objek `firebase`: `{signer_key, salt_separator, rounds, mem_cost}`.
* `GET imports/` menampilkan daftar batch yang sudah di-commit.

## Format file

### JSON

Sebuah array orang, atau objek yang `users`-nya berisi array tersebut.

| Kolom | Aturan |
| - | - |
| `email` | Wajib. |
| `external_id` | Hingga 255 karakter. Menjadi [`vendor_data`](https://docs.didit.me/id/entities/vendor-data-linking) orang tersebut. |
| `email_verified` | `true` atau `false` (juga `1`/`0`, `yes`/`no`). |
| `display_name` | Hingga 200 karakter. |
| `password_hash`, `password_salt`, `password_algorithm` | Lihat [kata sandi](#kata-sandi). |
| `totp_secret` | Rahasia autentikator dalam Base32. |
| `created_at`, `last_signed_in_at` | ISO 8601. Waktu tanpa zona dianggap UTC. |
| `social` | Hingga 8 `{provider, subject, tenant_id}`. `provider` adalah `google`, `apple`, `github` atau `microsoft`. `tenant_id` wajib hanya untuk Microsoft. |
| `organizations` | Hingga 20 `{slug, name, role}`. `role` adalah `admin`, `member` (default) atau `billing`. |

### CSV

Kolom bernilai tunggal yang sama, ditambah `google_id`, `apple_id`, `github_id`, `microsoft_id`, `microsoft_tenant_id`, `organization_slug`, `organization_name` dan `organization_role`. Orang yang berada di beberapa organisasi mendapat satu baris untuk setiap organisasi.

```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
```

Field atau kolom yang tidak dikenal adalah kesalahan dan tidak pernah diabaikan.

### Duplikat dan akun yang sudah ada

* Baris dengan email yang sama menjadi satu orang. Baris yang identik dihitung sebagai duplikat. Baris dengan lebih banyak tautan, organisasi, atau autentikator akan digabungkan.
* Dua hash kata sandi, rahasia autentikator, atau peran organisasi yang berbeda untuk satu orang adalah kesalahan. Begitu pula `external_id` atau subjek penyedia yang diklaim oleh dua orang.
* Jika akun dengan email tersebut sudah ada dan dibuat oleh impor sebelumnya, orang tersebut dilewati (`already_imported`). Jika orang tersebut mendaftar sendiri, hal itu menjadi kesalahan `identity_conflict`. Impor tidak pernah mengubah akun yang sudah ada.

## Kata sandi

| Format | Diterima |
| - | - |
| bcrypt | `$2a$`, `$2b$` atau `$2y$`, cost 4 sampai 13. |
| Argon2id | `$argon2id$v=19$...`. Hingga 64 MiB memori, hasil perkalian memori dan jumlah pass hingga 256 MiB, hingga 8 lane. |
| Django PBKDF2-SHA256 | `pbkdf2_sha256$...`, 10.000 sampai 2.000.000 iterasi. |
| Django scrypt | `scrypt$...`, work factor hingga 32.768, block size 1 sampai 8, parallelism 1 sampai 5. |
| Firebase scrypt | `password_algorithm: "firebase_scrypt"`, `password_hash` dan `password_salt` dalam base64 per pengguna, ditambah `signer_key`, `salt_separator`, `rounds` (1 sampai 8) dan `mem_cost` (1 sampai 14) milik proyek Anda, sekali per impor. |

Parameter Firebase disimpan terenkripsi, hanya untuk aplikasi tersebut. Format lain, atau cost di atas batas ini, berarti pengguna perlu membuat kata sandi baru. Setelah pengguna berhasil masuk untuk pertama kali, Didit menyimpan kata sandinya lagi dalam format Didit sendiri.

### Pengguna tanpa kata sandi yang dapat digunakan

Ketika pengguna tidak memiliki hash, atau hash-nya tidak didukung, percobaan kata sandi akan menghasilkan respons kata sandi salah seperti biasa. Didit juga mengirimkan email kepada mereka berisi tautan untuk mengatur kata sandi baru (berlaku 30 menit, paling banyak sekali sehari). Ini hanya terjadi jika email mereka terverifikasi, akun tidak diblokir, dan fitur masuk dengan kata sandi diaktifkan.

Dalam hasil, `reset_required` menghitung pengguna tanpa kata sandi yang dapat digunakan. `reset_emails` menghitung di antara mereka yang memiliki email terverifikasi: hanya mereka yang dapat dijangkau dengan cara ini. Pengguna dengan email yang belum terverifikasi tidak dapat masuk dengan kata sandi. Mereka tetap dapat masuk dengan kode email jika Anda mengaktifkan metode tersebut.

## Tautan sosial, autentikator, dan organisasi

* **Tautan sosial** disimpan untuk penyedia yang sudah dikonfigurasi aplikasi Anda, di bawah client ID saat ini. Dengan begitu, masuk berikutnya tidak memerlukan bukti email. Subjek Google dan GitHub bersifat global. Subjek Apple bersifat per tim pengembang dan subjek Microsoft bersifat per registrasi aplikasi, sehingga tautan tersebut hanya cocok jika platform lama Anda menggunakan tim atau registrasi yang sama. Tautan ke penyedia yang belum Anda konfigurasi dihitung sebagai `social_unavailable` dan tidak diimpor.
* **Aplikasi autentikator** (rahasia TOTP: SHA-1, 6 digit, 30 detik) disimpan terenkripsi. Pengguna yang diimpor harus menggunakannya sebagai faktor kedua. Mereka tidak mendapatkan kode pemulihan.
* **Organisasi** yang belum ada akan dibuat dari slug-nya.
* **Passkey** tidak dapat diimpor. Passkey terikat pada domain masuk lama Anda, sehingga pengguna perlu menambahkan passkey baru setelah mereka masuk dengan Didit.

## Hasil

Setiap uji coba, batch, dan proses mengembalikan `result` yang sama:

| Kolom | Arti |
| - | - |
| `rows`, `people_total` | Baris yang dibaca dan pengguna unik. |
| `ready`, `already_imported`, `duplicates`, `merged`, `skipped` | Apa yang terjadi pada setiap pengguna. |
| `passwords` | Jumlah per format: `bcrypt`, `argon2id`, `firebase_scrypt`, `pbkdf2_sha256`, `scrypt`. |
| `reset_required`, `reset_emails`, `unsupported_hashes` | Pengguna yang perlu membuat kata sandi baru. |
| `verified_emails` | Pengguna yang diimpor dengan email terverifikasi. |
| `social_links`, `social_unavailable` | Tautan yang disimpan per penyedia, dan tautan yang dilewati. |
| `authenticator_apps` | Pengguna dengan autentikator yang diimpor. |
| `organizations` | `count`, `members` dan hingga lima `names`. |
| `error_count`, `errors` | Hingga 100 error, masing-masing dengan `row` dengan indeks dimulai dari nol (baris CSV `row + 2`), `field` dan kode `error`. |

Respons tidak pernah berisi hash kata sandi, salt, rahasia autentikator, atau nilai kolom.

| Error | Arti |
| - | - |
| `file_too_large`, `too_many_rows`, `empty_file` | Lebih dari 25 MB, lebih dari 100.000 pengguna, atau kosong. |
| `invalid_encoding`, `invalid_json`, `invalid_csv` | File tidak dapat dibaca. |
| `unknown_column`, `missing_column`, `duplicate_column`, `unknown_field` | File tidak sesuai dengan format. |
| `required`, `invalid_field`, `invalid_row` | Sebuah nilai hilang atau tidak valid. |
| `identity_conflict` | Akun dengan email tersebut sudah mendaftar sendiri. |
| `social_link_conflict`, `password_conflict`, `authenticator_conflict`, `role_conflict` | Dua baris untuk satu pengguna tidak sesuai. |
| `too_many_uploads`, `import_running`, `not_startable`, `file_expired` | Proses tidak dapat dimulai sekarang. |

## Sebelum Anda beralih

Uji di aplikasi sandbox Anda:

* masuk dengan setiap format kata sandi yang Anda impor,
* email untuk mengatur kata sandi baru bagi pengguna tanpa hash yang dapat digunakan,
* masuk dengan akun sosial dan kode autentikator untuk pengguna yang diimpor,
* menjalankan file yang sama dua kali.

Lalu, pastikan `vendor_data` setiap pengguna sama dengan nilai yang Anda gunakan untuk [verifikasi dan transaksi](https://docs.didit.me/id/authentication/overview.md#pertahankan-satu-orang-di-seluruh-produk) mereka.


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