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

# 사용자 마이그레이션: 비밀번호 및 MFA 가져오기

> CSV나 JSON 파일, 또는 일괄 방식으로 비밀번호, 인증된 이메일, 소셜 연결, 인증 앱, 조직 역할을 포함한 사용자를 Didit으로 가져올 수 있습니다.

> [!WARNING]
>
> 인증 기능은 새로운 기능입니다. 모든 가져오기는 먼저 샌드박스 애플리케이션에서 실행한 다음 라이브 애플리케이션으로 가져오세요.

가져오기는 다른 신원 인증 플랫폼의 사용자를 Didit Authentication으로 옮깁니다. 비밀번호, 인증된 이메일, 소셜 로그인 연결, 인증 앱, 조직 역할이 함께 이동합니다. 애플리케이션이 Didit으로 사용자를 로그인시키기 전까지 사용자에게 달라지는 것은 없습니다.

가져오기는 애플리케이션의 Authentication 관리 API를 사용하며, `write:authentication`(목록 조회만 하려면 `read:authentication`) 권한이 있는 콘솔 세션이 필요합니다. 최종 사용자 토큰이나 인증 API 키는 사용하지 않습니다. 예시에서 `MANAGEMENT_BASE`는 다음과 같습니다.

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

## 가져오기 방식 두 가지

| | 파일 작업 | 배치 요청 |
| - | - | - |
| 엔드포인트 | `POST import-jobs/` (multipart) | `POST imports/` (JSON) |
| 크기 | CSV 또는 JSON 파일 1개, 최대 25MB 및 100,000명 | 요청당 1\~1,000개의 `rows` |
| 드라이 런 | 항상 먼저 백그라운드에서 실행됩니다 | 기본값은 `dry_run: true` |
| 커밋 | `POST import-jobs/{id}/start/` | 같은 요청을 `dry_run: false`로 보냅니다 |
| 부분 실패 | 1,000명 단위 배치는 각각 독립적으로 커밋되며, 중단된 가져오기는 이어서 진행됩니다 | 전부 적용되거나 전부 적용되지 않음 |

## 파일 가져오기

### 1. 업로드

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

| 필드 | 의미 |
| - | - |
| `file` | CSV 또는 JSON, UTF-8(바이트 순서 표시 허용), 최대 25MB. |
| `passwords` | `import`(기본값)는 비밀번호 해시를 유지합니다. `reset`은 해시를 무시하므로 모든 사용자가 새 비밀번호를 설정해야 합니다. |
| `keep_social` | 소셜 로그인 연결을 유지합니다. |
| `keep_verified` | `email_verified` 값을 유지합니다. `false`로 설정하면 모든 이메일이 미인증 상태가 됩니다. |
| `keep_mfa` | 인증 앱 시크릿을 유지합니다. |
| `keep_ids` | `external_id`를 유지합니다. `false`로 설정하면 `vendor_data`가 Didit 신원 ID가 됩니다. |
| `firebase_signer_key`, `firebase_salt_separator`, `firebase_rounds`, `firebase_mem_cost` | Firebase 프로젝트의 해시 파라미터입니다. 네 가지를 모두 보내거나 하나도 보내지 마세요. |

각 `keep_*` 스위치는 `false`를 보내지 않으면 `true`입니다. 호출이 성공하면 `202`와 작업이 반환됩니다. 애플리케이션은 동시에 최대 5개의 업로드 파일을 보관할 수 있습니다.

### 2. 드라이 런 결과 확인

모든 업로드는 드라이 런으로 시작합니다. 드라이 런은 모든 사용자를 검사하지만 아무것도 기록하지 않습니다. `GET import-jobs/{id}/`를 주기적으로 호출하여 확인하세요.

| 필드 | 의미 |
| - | - |
| `kind` | 드라이 런은 `dry_run`이며, 시작한 뒤에는 `import`입니다. |
| `status` | `queued`, `running`이 나타나며, 드라이 런은 `passed` 또는 `failed`, 가져오기는 `done` 또는 `error`로 끝납니다. |
| `processed`, `total` | 진행 상황(사용자 수 기준)입니다. |
| `result` | 파일의 내용입니다([결과](#결과) 참고). |
| `startable` | 드라이 런이 통과했고 파일이 만료되지 않았으면 `true`입니다. |
| `expires_at` | 가져오지 않은 드라이 런 이후 24시간이 지나면 파일이 삭제됩니다. |

드라이 런이 실패하면 오류 목록이 표시됩니다. 파일을 수정한 뒤 다시 업로드하세요.

### 3. 가져오기 시작

`POST import-jobs/{id}/start/`는 드라이 런에서 검사한 내용을 가져오며 `202`를 반환합니다.

* 사용자는 1,000명 단위 배치로 가져옵니다. 각 배치는 독립적으로 커밋됩니다.
* 애플리케이션당 한 번에 하나의 가져오기만 실행됩니다(그렇지 않으면 `import_running`).
* 중단된 가져오기는 마지막으로 커밋된 배치 다음부터 이어서 진행됩니다.
* 이미 가져온 사용자는 건너뛰므로 같은 파일을 다시 실행해도 됩니다.
* 가져오기를 시작한 사람은 완료되거나 중단될 때 이메일을 받습니다.

업로드된 파일은 암호화되어 저장됩니다. 가져오기가 끝나거나, 드라이 런이 실패하거나, 가져오지 않은 드라이 런 이후 24시간이 지나면 삭제됩니다.

## 배치로 가져오기

요청당 최대 1,000명을 보낼 수 있으며, `idempotency_key`는 최대 100자입니다.

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

* 드라이 런은 `200`과 결과를 반환하며 아무것도 기록하지 않습니다.
* `dry_run: false`로 보내면 성공한 가져오기는 `201`을 반환합니다. 오류가 하나라도 있는 배치는 `400`을 반환하며 아무도 가져오지 않습니다.
* 같은 키로 이미 커밋된 요청을 반복하면 기록된 결과를 반환합니다. 같은 키에 내용이 다르면 `400 {"idempotency_key": "different_payload"}`를 반환합니다.
* Firebase 파라미터는 `firebase` 객체에 넣습니다: `{signer_key, salt_separator, rounds, mem_cost}`.
* `GET imports/`는 커밋된 배치를 나열합니다.

## 파일 형식

### JSON

사용자 배열이거나, `users`에 배열을 담은 객체입니다.

| 필드 | 규칙 |
| - | - |
| `email` | 필수입니다. |
| `external_id` | 최대 255자입니다. 사용자의 [`vendor_data`](https://docs.didit.me/ko/entities/vendor-data-linking)가 됩니다. |
| `email_verified` | `true` 또는 `false`(`1`/`0`, `yes`/`no`도 가능). |
| `display_name` | 최대 200자입니다. |
| `password_hash`, `password_salt`, `password_algorithm` | [비밀번호](#비밀번호)를 참고하세요. |
| `totp_secret` | Base32 인증 앱 시크릿입니다. |
| `created_at`, `last_signed_in_at` | ISO 8601 형식입니다. 시간대가 없으면 UTC로 간주합니다. |
| `social` | 최대 8개의 `{provider, subject, tenant_id}`. `provider`는 `google`, `apple`, `github` 또는 `microsoft`입니다. `tenant_id`는 Microsoft에만 필요합니다. |
| `organizations` | 최대 20개의 `{slug, name, role}`. `role`은 `admin`, `member`(기본값) 또는 `billing`입니다. |

### CSV

단일 값 열은 JSON과 같으며, 여기에 `google_id`, `apple_id`, `github_id`, `microsoft_id`, `microsoft_tenant_id`, `organization_slug`, `organization_name`, `organization_role` 열이 추가됩니다. 여러 조직에 속한 사용자는 조직마다 한 행을 차지합니다.

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

알 수 없는 필드나 열은 무시하지 않고 오류로 처리합니다.

### 중복 및 기존 계정

* 이메일이 같은 행은 한 사람으로 합쳐집니다. 내용이 완전히 같은 행은 중복으로 집계합니다. 연결, 조직 또는 인증 앱이 더 많은 행은 병합됩니다.
* 한 사람에게 서로 다른 비밀번호 해시, 인증 앱 시크릿 또는 조직 역할이 두 개 이상 있으면 오류입니다. 두 사람이 같은 `external_id` 또는 제공자 subject를 주장하는 경우도 오류입니다.
* 같은 이메일의 계정이 이미 있고 이전 가져오기로 만들어진 계정이라면 해당 사용자는 건너뜁니다(`already_imported`). 사용자가 직접 가입한 계정이라면 `identity_conflict` 오류가 발생합니다. 가져오기는 기존 계정을 절대 변경하지 않습니다.

## 비밀번호

| 형식 | 허용 범위 |
| - | - |
| bcrypt | `$2a$`, `$2b$` 또는 `$2y$`, 비용 4\~13. |
| Argon2id | `$argon2id$v=19$...`. 메모리 최대 64 MiB, 메모리 × 반복 횟수 최대 256 MiB, 레인 최대 8개. |
| Django PBKDF2-SHA256 | `pbkdf2_sha256$...`, 반복 횟수 10,000\~2,000,000회. |
| Django scrypt | `scrypt$...`, 작업 계수 최대 32,768, 블록 크기 1~~8, 병렬도 1~~5. |
| Firebase scrypt | `password_algorithm: "firebase_scrypt"`, 사용자별 base64 `password_hash`와 `password_salt`, 그리고 가져오기 시 한 번만 지정하는 프로젝트의 `signer_key`, `salt_separator`, `rounds`(1~~8), `mem_cost`(1~~14). |

Firebase 파라미터는 암호화되어 저장되며, 해당 애플리케이션에서만 사용됩니다. 그 밖의 형식이거나 비용이 위 제한을 넘는 경우에는 해당 사용자가 새 비밀번호를 설정해야 합니다. 사용자가 처음으로 성공적으로 로그인하면 Didit은 해당 비밀번호를 Didit 고유 형식으로 다시 저장합니다.

### 사용 가능한 비밀번호가 없는 사용자

해시가 없거나 지원되지 않는 해시를 가진 사용자가 비밀번호로 로그인을 시도하면 일반적인 비밀번호 불일치 응답을 받습니다. 또한 Didit은 새 비밀번호를 설정할 수 있는 링크를 이메일로 보냅니다(유효 기간 30분, 하루 최대 1회). 이 링크는 이메일이 인증되어 있고, 계정이 차단되지 않았으며, 비밀번호 로그인이 켜져 있는 경우에만 발송됩니다.

결과에서 `reset_required`는 사용 가능한 비밀번호가 없는 사용자 수를 나타냅니다. `reset_emails`는 그중 인증된 이메일을 가진 사용자 수입니다. 이 방법으로 연락할 수 있는 사용자는 이들뿐입니다. 이메일이 인증되지 않은 사용자는 비밀번호로 로그인할 수 없습니다. 이메일 코드 방식을 활성화했다면 이메일 코드로는 로그인할 수 있습니다.

## 소셜 연동, 인증 앱 및 조직

* **소셜 연동**은 애플리케이션에 이미 구성된 제공자에 대해, 현재 클라이언트 ID 기준으로 유지됩니다. 이후 로그인할 때는 이메일 인증이 필요하지 않습니다. Google과 GitHub의 subject는 전역적으로 유효합니다. Apple의 subject는 개발자 팀별로, Microsoft의 subject는 앱 등록별로 구분됩니다. 따라서 기존 플랫폼이 같은 팀이나 등록 정보를 사용한 경우에만 해당 연동이 일치합니다. 구성되지 않은 제공자에 대한 연동은 `social_unavailable`로 집계되며 가져오지 않습니다.
* **인증 앱**(TOTP 비밀 키: SHA-1, 6자리, 30초)은 암호화되어 저장됩니다. 가져온 사용자는 이를 2단계 인증 수단으로 사용해야 합니다. 이들에게는 복구 코드가 발급되지 않습니다.
* **아직 존재하지 않는 조직**은 슬러그를 기준으로 생성됩니다.
* **패스키**는 가져올 수 없습니다. 패스키는 기존 로그인 도메인에 속하므로, 사용자는 Didit으로 로그인한 뒤 새 패스키를 추가해야 합니다.

## 결과

모든 시험 실행, 배치 및 작업은 같은 `result`를 반환합니다.

| 필드 | 의미 |
| - | - |
| `rows`, `people_total` | 읽은 줄 수와 고유 사용자 수. |
| `ready`, `already_imported`, `duplicates`, `merged`, `skipped` | 각 사용자에게 일어나는 처리 결과. |
| `passwords` | 형식별 개수: `bcrypt`, `argon2id`, `firebase_scrypt`, `pbkdf2_sha256`, `scrypt`. |
| `reset_required`, `reset_emails`, `unsupported_hashes` | 새 비밀번호가 필요한 사용자. |
| `verified_emails` | 인증된 이메일을 가진 채 가져온 사용자. |
| `social_links`, `social_unavailable` | 제공자별로 유지된 연동 수와 건너뛴 연동 수. |
| `authenticator_apps` | 가져온 인증 앱을 보유한 사용자. |
| `organizations` | `count`, `members`, 그리고 최대 5개의 `names`. |
| `error_count`, `errors` | 최대 100개의 오류. 각 오류에는 0부터 시작하는 `row`(CSV 줄 번호는 `row + 2`), `field`, `error` 코드가 포함됩니다. |

응답에는 비밀번호 해시, 솔트, 인증 앱 비밀 키, 필드 값이 절대 포함되지 않습니다.

| 오류 | 의미 |
| - | - |
| `file_too_large`, `too_many_rows`, `empty_file` | 25 MB를 초과하거나, 사용자가 100,000명을 초과하거나, 파일이 비어 있습니다. |
| `invalid_encoding`, `invalid_json`, `invalid_csv` | 파일을 읽을 수 없습니다. |
| `unknown_column`, `missing_column`, `duplicate_column`, `unknown_field` | 파일이 형식과 일치하지 않습니다. |
| `required`, `invalid_field`, `invalid_row` | 값이 없거나 잘못되었습니다. |
| `identity_conflict` | 해당 이메일로 계정이 이미 별도로 가입되었습니다. |
| `social_link_conflict`, `password_conflict`, `authenticator_conflict`, `role_conflict` | 같은 사용자에 대한 두 줄의 값이 서로 일치하지 않습니다. |
| `too_many_uploads`, `import_running`, `not_startable`, `file_expired` | 지금은 작업을 시작할 수 없습니다. |

## 전환하기 전에

샌드박스 애플리케이션에서 다음을 테스트하십시오.

* 가져온 각 비밀번호 형식으로 로그인,
* 사용 가능한 해시가 없는 사용자의 새 비밀번호 설정 이메일,
* 가져온 사용자의 소셜 로그인과 인증 앱 코드,
* 같은 파일을 두 번 실행하는 경우.

그다음 각 사용자의 `vendor_data`가 [인증 및 거래](https://docs.didit.me/ko/authentication/overview.md#여러-제품에서-한-사람-유지하기)에서 사용하는 값과 일치하는지 확인하십시오.


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