# 初期設定、デバイス QR コードと 6 桁コードによるログイン

> HTML: https://remote.ivjn.us/docs/ja/pairing/

Agent の既定の Exchange は `wss://exchange.ivjn.us`、CLI/Console の管理 API アドレスは `https://exchange.ivjn.us` です。既存の設定を優先し、独自にホストする Exchange のアドレスは上書きしません。6 桁コード機能を利用する前に、必ず Exchange をアップグレードしてください。

## SaaS 登録と初回ログイン

```powershell
remote-cli register --username alice --email alice@example.com
# 按隐藏输入提示设置密码，注册后向邮箱发送验证码。
remote-cli verify-email --username alice
# 隐藏输入邮件中的 8 位数字验证码，然后登录。
remote-cli login --email alice@example.com
# init 是 login 的别名，不带 --email 时交互输入邮箱和密码。
remote-cli init
# 原有令牌仍可使用：
remote-cli login --server https://exchange.ivjn.us --token-stdin
```

登録とログインは既定で `https://exchange.ivjn.us` に接続します。独自サーバーを使用する場合は `--server` を追加してください。ログインにはメールアドレスをアカウント識別子として使用します。アドレスの空白を除去し、小文字に変換し、不正な形式は直ちに拒否します。ユーザー名は登録時のワークスペース名（英数字または `._-` の 3～64 文字）です。アカウントのパスワードには長さや文字種の制限はありません。サーバーはパスワードのハッシュのみを保存します。自動化には `--password-stdin` を使用し、コマンド引数にパスワードを記述しないでください。新規アカウントはメールの確認が完了するまでログインできません。CLI は返されたセッショントークンのみを保存し、パスワードは保存しません。登録後の確認待ちの間は既存のログインを維持します。

登録ごとに独立したワークスペースを作成し、そのアカウントをワークスペースの管理者にします。デバイス、メンバー、リモートセッション、メトリクス、監査はすべて分離され、異なるワークスペースでは同じデバイス ID を使用できます。6 桁コードの承認により、デバイスまたは新しいメンバーが**承認者のワークスペース**に追加されます。既存のワークスペースに参加する場合は、その管理者から提供されたトークンまたは 6 桁コードによる承認を使用してください。

Android Console も、登録、パスワードログイン、既存のトークンによるログイン、6 桁コードによるログインをサポートします。パスワードは現在のリクエストにのみ使用し、認証情報は引き続き SecureStore に保存します。

パスワードログインのセッションは 30 日間有効で、各アカウントは最大 32 個の有効なセッションを持てます。再ログインしても他のクライアントはログアウトしません。`POST /api/auth/logout` は現在の Bearer セッショントークンを使用してログアウトし、確立済みの WebSocket は最大 5 秒以内に閉じます。有効期限が切れたら再ログインしてください。デバイストークンと既存の運用トークンは従来のライフサイクルを維持します。

トークンのみの ID にはパスワードアカウントがないため、メールによる復旧はできません。現時点では課金やインスタンス間の WebSocket ルーティングは含まれません。

## メール確認とパスワードの復旧

```powershell
remote-cli resend-verification --email alice@example.com
remote-cli verify-email --username alice
remote-cli forgot-password --email alice@example.com
remote-cli reset-password --email alice@example.com
# 按提示输入邮件验证码、新密码，再重新登录。

# 已登录的旧密码账号绑定或更换邮箱，需要再次验证当前密码。
remote-cli set-email --email alice@example.com
remote-cli verify-email --username alice
```

確認と復旧では用途の異なる 8 桁の数字の確認コードを使用します。有効期間は 15 分で、連続して 5 回誤入力すると無効になり、メールを再送すると旧コードは無効になります。各アカウントは用途ごとに 1 分に 1 回だけ送信できます。確認とリセットのコマンドは `--code` で自動化できます。既定ではコードを非表示で入力します。リセットが成功すると、そのアカウントのすべてのログインセッションを取り消し、確立済みの WebSocket は最大 5 秒以内に切断します。ワークスペース内の他のメンバーやデバイスには影響しません。

Console の登録ページでメールアドレスを入力すると、確認ページに進みます。ログインページには **メールを確認** と **パスワードを忘れた場合** があります。メールアドレスの変更では、確認が完了するまで元のアドレスを保持します。確認メールの再送と復旧リクエストは共通の案内を返し、メールアドレスが登録済みかどうかを明かしません。

メール機能には `0004_email.sql` のマイグレーションと SMTP 設定が必要です。設定項目は `SMTP_HOST`、`SMTP_PORT`、`SMTP_FROM`、`SMTP_USERNAME`、`SMTP_PASSWORD`、`SMTP_SECURITY` です。既定は STARTTLS で、暗黙的 TLS もサポートします。暗号化しない SMTP はローカルテストのループバックアドレスでのみ許可します。SMTP 認証情報はサービスの環境ファイルに保存し、Git にコミットしないでください。メールサービスを設定しない場合、新バージョンの登録とメール送信の API は 503 を返しますが、既存アカウントのログインには影響しません。そのため、本番で有効にする前に必ず送信設定を完了してください。SMTP の一時的な失敗で確認待ちアカウントが残る場合があります。復旧後は確認メールを再送し、再登録する必要はありません。

## Exchange アカウント API

| メソッドとパス | 認証 | 用途 |
| --- | --- | --- |
| `POST /api/auth/register` | 不要、レート制限あり | `{username,password,email}`。確認待ちアカウントを作成し、202 と `verification_required` を返します。セッションは発行しません |
| `POST /api/auth/login` | 不要、レート制限あり | `{email,password}` で独立したログインセッションを発行 |
| `GET /api/auth/me` | 運用トークンまたはログインセッション | `name`、`tenant_id`、`role` を返します |
| `POST /api/auth/logout` | 現在のログインセッション | 現在のセッションを取り消します。他のクライアントは取り消しません |
| `POST /api/auth/verify-email` | 不要、レート制限あり | `{username,code}`。メール確認コードを消費します |
| `POST /api/auth/resend-verification` | 不要、レート制限あり | `{email}`。確認待ちアドレスにメールを再送します |
| `POST /api/auth/forgot-password` | 不要、レート制限あり | `{email}`。確認済みアドレスに復旧コードを送信します |
| `POST /api/auth/reset-password` | 不要、レート制限あり | `{email,code,password}`。パスワードを変更し、旧セッションを取り消します |
| `POST /api/auth/email` | 現在のログインセッションとアカウントのパスワード | `{email,password}`。メールの登録または変更を申請し、確認後に有効になります |

ログインが成功すると `username`、`email`（登録済みの場合）、`tenant_id`、`token`、`expires_in` を返し、`Cache-Control: no-store` を設定します。アカウントのパスワードは Argon2id の既定パラメーターでソルトを付けて保存します。TCP 接続元 IP ごとのアカウントリクエストは毎分最大 20 回、ユーザー名ごとは毎分最大 10 回で、パスワードハッシュの同時処理は最大 4 件です。クライアントが提供する転送 IP は信頼しません。リバースプロキシ配下のクライアントは IP ごとの枠を共有するため、公開環境では信頼できる入口に追加のレート制限を設定してください。

Rust Exchange の公開登録は既定で無効です。`EXCHANGE_ALLOW_REGISTRATION=true` と SMTP の設定で明示的に有効化します。`false` は新規登録を無効にし、既存アカウントは引き続きログインできます。

## Agent に 6 桁コードを表示する

Android Remote Agent では既定のアドレスが入力済みです。**Connect with a six-digit code** をタップしてください。アプリが 5 分間有効な 6 桁コードを表示します。ログイン済みの管理者 CLI で次を実行します。

```powershell
remote-cli pair 012345
# 可选：覆盖默认 ID 和显示名称
remote-cli pair 012345 --device-id my-android --name "我的手机"
```

CLI は接続コードを取得するとデバイス名、ID の種類、推奨デバイス ID を表示します。自分のデバイスであることを確認して承認してください。既定の ID はデバイス名の小文字の ASCII 英字、数字、`._-` を使い、空白とその他の文字を取り除きます。使用できる文字がない場合は `device` を使用し、最大 64 文字に制限します。同じワークスペース内で名前が重複する場合は `realmev5`、`realmev51`、`realmev52` のように順に数字を追加し、必要に応じて元の ID を短くします。Exchange は作成時に同時実行の競合を処理し、CLI は最終的に割り当てられた ID を表示します。手動指定の ID が重複する場合も数字を追加します。Android はシステムのデバイス名（取得できない場合はモデル名）、ネイティブ Agent はホスト名を使用します。Agent は独立したデバイストークンを自動取得して暗号化保存し、接続を開始します。ユーザー補助や画面制御は自動的に有効にしません。キャンセル、有効期限切れ、認証失敗では既存の設定を置き換えません。Agent が接続中の場合は先に停止してください。

## PC、ルーター、Linux Agent へのデバイス追加

Windows、macOS、OpenWrt、Linux Agent はデバイストークンを手動入力する必要がありません。次のいずれかの方法を選んでください。

| プラットフォーム | 方法 1：ペアリングコード / デバイス QR コード | 方法 2：招待を貼り付ける |
| --- | --- | --- |
| Windows / macOS（デスクトップ `--ui`、ダブルクリックで起動） | ローカルページの **Add this device → Get pairing code** で 6 桁コードと QR コードを表示 | ローカルページの **Invite** テキストボックスに貼り付け、**Join with invite** をクリック |
| OpenWrt（luci-app-remote） | LuCI の **サービス → リモート管理 → デバイスを追加 → ペアリングコードを取得** で 6 桁コードと QR コードを表示 | 同じページの **方法 2：招待内容** に貼り付け、**参加** をクリック |
| Linux / 全プラットフォームのコマンドライン | `remote-agent pair`（端末に 6 桁コードと文字で構成した QR コードを表示） | `remote-agent join '<邀请内容>'`、または `remote-agent join -` で標準入力から読み込み |

**方法 1**：Agent は 5 分間有効な 6 桁コードを要求し、同時に QR コードを表示します。内容は `remote://pair?code=012345&server=wss%3A%2F%2Fexchange.ivjn.us` で、認証情報は含みません。管理者は次のいずれかで承認できます。
Android Console の **デバイスを追加 → ペアリングコードを入力 → デバイス QR コードをスキャン**（カメラでスキャンし、サーバーが一致することを確認してから照会し、承認を確定）、または 6 桁コードを入力します。Web Console の **ペアリングコードを承認** では、6 桁コードの入力または `remote://pair?...` の貼り付けができます。`remote-cli pair 012345`（この URI も受け付けます）も使用できます。承認後、Agent は自動的にトークンを保存して接続します。

**方法 2**：管理者は Android Console の **デバイスを追加 → スキャンして追加** で使い捨ての招待を生成し、**招待をコピー（PC / ルーターに貼り付け）** をタップします。または `remote-cli qr --device-id pc-01 --text` を実行します。招待は QR コード内の JSON（`{"type":"remote-enrollment",...}`）で、5 分以内に 1 回だけ使用できます。デバイストークン QR コード（`remote-cli device qr`）の内容も受け付けます。

保存先と接続：

- デスクトップ UI：現在の設定ファイル（既定は `%LOCALAPPDATA%\remote-agent\config.json` / `~/.config/remote-agent/config.json`）に書き込み、直ちに接続します。
- OpenWrt：UCI の `remote-agent.main`（server / device_id / token、`enabled=1`）に書き込み、procd サービスを再起動します。
- コマンドライン：既定ではデスクトップ用の設定パスに書き込みます（OpenWrt の既定は UCI）。`--config PATH` で JSON ファイルを指定し、`--uci` で UCI への書き込みを強制できます。保存後はフォアグラウンドで接続します（`--no-connect` は保存のみ）。既にサービスがその設定を使用している場合は、サービスを再起動してください。

認証情報が設定済みの場合、既定では置き換えを拒否します。コマンドラインでは `--force` が必要で、デスクトップ UI と LuCI は先に確認を求めます。既存のトークン方式（`--device-id/--token`、設定ファイル、LuCI 設定ページ）は従来どおりです。

```sh
remote-agent pair                       # 显示六位码 + 二维码，批准后保存并连接
remote-agent pair --server wss://your-exchange.example.com --config ./agent.json
remote-agent join '{"type":"remote-enrollment","version":1,...}'
remote-agent join - < invite.txt --no-connect
```

ネイティブのペアリングには `wss://` が必要です。テスト用にローカルのループバックでのみ `ws://` を許可します。Android と Console のペアリングには HTTPS/WSS が必要です。

## Console の 6 桁コードによるログイン

Android Remote Console のログインページで **6 桁コードでログイン** をタップします。Web Console（`/console`）ではログインカードの **6 桁コード** タブを選び、ログインコードを取得するボタンをクリックします。管理者は次のいずれかで承認できます。

```powershell
remote-cli pair 012345 --operator-name mobile-alice
# 只读客户端：追加 --role readonly
```

またはログイン済みの **Web Console** ワークスペースで **ペアリングコードを承認**（管理者のみ表示され、読み取り専用オペレーターには表示されません）をクリックし、相手の画面の 6 桁コードを入力して、種類とラベルを確認してから承認します。

既定では独立した `operator` アカウントを作成し、管理者自身のトークンをコピーしません。管理者は `--role admin` または `--role readonly` を明示的に指定できます。Android Console は承認を受け取ると認証情報を再検証し、SecureStore に保存してデバイス一覧に進みます。Web Console はオペレーターの認証情報を現在のページのメモリ内に保持し（アカウント / トークンログインと同じ）、デバイス一覧に進みます。キャンセルや失敗の後も既存のアカウントまたはトークンでログインでき、その後に別のログイン方法で入力した内容は消去しません。

## Remote Agent で CLI の QR コードをスキャンしてログイン

```powershell
remote-cli qr --device-id my-phone
# 输出 SVG 供手机扫描：
remote-cli qr --device-id my-phone --output ./phone-login.svg
```

ログイン済みの管理者 CLI で QR コードを生成し、スマートフォンの Remote Agent で **Scan connection QR code** をタップします。サーバーとデバイス ID を確認し、**Connect** をタップしてください。QR コードに含まれるのはランダムな 256 ビットの一時チケットのみで、有効期間は 5 分、1 台のデバイスにだけ関連付けできます。長期のデバイストークンは含みません。スキャン後、Exchange がデバイストークンを発行し、スマートフォンは Keystore で保存して接続します。QR コードのデバイス ID は新しいものにする必要があります。既存のデバイスを置き換えたり、トークンをローテーションしたりすることはありません。

同じスマートフォンからの受け取りリクエストは固定のランダムな `claim_id` を含むため、ネットワーク切断後に再試行して同じ結果を取得できます。異なる `claim_id` では重複して受け取れません。保存後、スマートフォンはチケットの消去を確認します。一時チケットはプロセスのメモリに保存され、Exchange を再起動すると無効になります。受け取りを開始済みの結果は、元のクライアントが再試行できるよう、さらに最大 5 分間保持します。

| メソッドとパス | 認証 | 用途 |
| --- | --- | --- |
| `POST /api/pairing/invite` | 管理者 | `{device_id,name?}`。チケットを生成します。管理者ごとに毎分 10 回、全体で 256 個に制限します |
| `POST /api/pairing/claim` | `Bearer <ticket>` | `{claim_id}`。独立したデバイス認証情報を返します。再試行では同じ 32 桁の 16 進数の `claim_id` を使用する必要があります |
| `POST /api/pairing/claim/complete` | 同上 | 保存成功後に消去を確認します |

## デバイス QR コードの生成

```powershell
# 创建设备，同时在终端显示二维码
remote-cli device add my-android --qr
# 新建 SVG 文件，可在电脑打开后供手机扫描
remote-cli device add another-phone --qr-output ./another-phone.svg
# 使用手里已有的设备令牌，不创建或轮换设备
remote-cli device qr my-android --output ./my-android.svg
# 明确轮换令牌并生成二维码；原令牌立即失效
remote-cli device rotate-token my-android --qr
```

`device qr` はデバイストークンを非表示入力で読み込み、`--token-stdin` もサポートします。デバイス QR コードに含められるのはデバイストークンのみで、運用トークンは使用できません。出力ファイルは上書きを拒否します。認証情報を含むため安全に管理し、使用後に削除してください。QR コードには HTTPS/WSS が必要です。

Android Console では、新しいデバイスの作成後にデバイス QR コードをそのまま表示します。デバイス詳細の **トークンをローテーションして QR コードを生成** では、先に切断の影響を説明して確認を求めます。サーバーはトークンのハッシュのみを保存しているため、既存のデバイスレコードから旧 QR コードを復元できません。そのため、QR コードを表示する目的で黙ってトークンをローテーションすることはありません。

## Exchange ペアリング API

| メソッドとパス | 認証 | 用途 |
| --- | --- | --- |
| `POST /api/pairing/start` | 不要、レート制限あり | `kind: device/operator`、`label` を送信し、`code`、`poll_token`、`expires_in: 300`、`interval: 3` を取得します |
| `GET /api/pairing/{code}` | 管理者の運用トークン | 申請の種類、ラベル、残り時間を確認します。認証情報は返しません |
| `POST /api/pairing/approve` | 管理者の運用トークン | `code`、`kind`、`identity`、任意の `name`、`role` を送信し、独立したデバイスまたはアカウントを作成します |
| `POST /api/pairing/poll` | `Authorization: Bearer <poll_token>` | `pending` または `approved` を返します。承認結果は `credentials.kind/identity/token` を含みます |
| `POST /api/pairing/complete` | 同上 | クライアントの保存成功後に一時的な承認を消去します。繰り返し呼び出しても安全です |
| `GET /ws/pairing` | 不要、レート制限あり | ネイティブ Agent のペアリング用 WebSocket です。順に `code`、`approved` または `expired` イベントを受信し、保存後にテキスト `complete` を送信します |

6 桁コードは認証済み管理者がリクエストを特定して確認するためだけに使用し、ポーリングや認証情報の取得には使用できません。ポーリング認証情報はランダムな 256 ビットの秘密値で、URL には含めません。重複承認は拒否します。ID の作成に失敗した場合は承認の占有状態を解除して再試行できるようにします。承認結果は同じ非公開のポーリング認証情報で再取得できるため、1 回のネットワークパケットの損失で認証情報が永久に失われることはありません。クライアントは保存後に消去を確認します。

承認待ちと、承認後の受け取り待ちはそれぞれ最大 5 分間保持します。`REDIS_URL` が未設定の場合、一時セッションは現在の Exchange プロセスのメモリにのみ保存し、再起動後はコードの再取得が必要です。複数インスタンスではペアリングリクエストを同じインスタンスに固定する必要があります。Redis を設定すると、ペアリングエントリとレート制限カウンターを Redis に保存します（5 分の TTL に合わせます）。複数インスタンスに自由に分配でき、スティッキーセッションは**不要**です。実際の TCP 接続元 IP ごとに毎分最大 10 回のコード取得、管理者ごとに毎分最大 20 回の照会または承認を許可します。メモリモードでは、さらに全体の保留セッション数を最大 256 個に制限します。プロキシが転送するクライアント IP は信頼できるレート制限の根拠としません。同じリバースプロキシ配下のユーザーは接続元の枠を共有します。期限切れのデータは TTL / 定期クリーンアップで消去します。レスポンスには `Cache-Control: no-store` を設定し、ペアリングの秘密値や発行済みトークンの平文はログに記録しません。

バックエンドに存在するデバイス ID / アカウントはペアリングリクエストで上書きできません。管理者はコードと申請元を確認してください。他人に誘導されて提示されたログインコードを承認しないでください。

接続コード API：`POST /api/pairing/start` は任意の `device_name` を受け付け、レスポンスでそのデバイス名を返します。管理者用の `GET /api/pairing/{code}` は `device_name` と現在のワークスペースの `suggested_device_id` を返します。照会では ID を予約しません。`POST /api/pairing/approve` のデバイスの `identity` は省略できます。成功時は 200 と `{identity}`（最終 ID、トークンを含まない）を返します。ネイティブ WebSocket の `/ws/pairing?device_name=...` は URL エンコードされたデバイス名をサポートします。
