remote.DocumentationHome

Initialization, Device QR Codes, and Six-Digit Code Login

The Agent defaults to Exchange at wss://exchange.ivjn.us; the CLI/Console management API uses https://exchange.ivjn.us. Existing configuration takes precedence, so self-hosted Exchange addresses are preserved.

SaaS Registration and First Login

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

Registration and login connect to https://exchange.ivjn.us by default; add --server for a self-hosted service. Login uses the email address as the account identity. Spaces are removed and the address is converted to lowercase; invalid formats are rejected immediately. The username is the workspace name chosen during registration (3–64 letters, digits, or ._-). Account passwords have no length or character-format restrictions; the server stores only password hashes. Automation can use --password-stdin; do not put passwords in command arguments. New accounts cannot log in until their email address is verified. The CLI stores only the returned session token, never the password, and preserves an existing login while registration awaits verification.

Each registration creates a separate workspace, with the registering account as its administrator. Devices, members, remote sessions, metrics, and audit records are isolated; different workspaces may use the same device ID. Approving a six-digit code adds the device or new member to the approver's workspace. To join an existing workspace, use a token or six-digit authorization provided by its administrator.

Android Console also supports registration, password login, existing-token login, and six-digit code login. Passwords are used only for the current request; credentials are stored in SecureStore.

Password login sessions last 30 days, with up to 32 active sessions per account; logging in again does not evict other clients. POST /api/auth/logout revokes the current Bearer session token, and established WebSockets close within at most 5 seconds. Log in again after expiry. Device tokens and existing operational tokens retain their original lifetimes.

Token-only identities have no password account and cannot recover access by email. Billing and cross-instance WebSocket routing are currently outside the scope of this implementation.

Email Verification and Password Recovery

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

Verification and recovery use separate, purpose-specific eight-digit codes. Codes expire after 15 minutes or five consecutive incorrect attempts; sending a new email invalidates the previous code. Each account may request one email per purpose per minute. Verification and reset commands accept --code for automation; by default, code entry is hidden. A successful reset revokes every login session for that account, with established WebSockets disconnected within at most 5 seconds. Other workspace members and devices are unaffected.

Enter an email address on the Console registration page, then continue to verification. The login page provides Verify email and Forgot password actions. When changing an email address, the old address remains active until verification completes. Resend-verification and recovery requests return the same generic message regardless of whether the email is registered.

Email requires migration 0004_email.sql and SMTP configuration: SMTP_HOST, SMTP_PORT, SMTP_FROM, SMTP_USERNAME, SMTP_PASSWORD, and SMTP_SECURITY. STARTTLS is the default; implicit TLS is also supported. Unencrypted SMTP is allowed only on loopback addresses for local testing. Keep SMTP credentials in the service environment file and out of Git. Without email configuration, the new registration and email-sending endpoints return 503; existing account login remains available. Configure email delivery before enabling production registration. A temporary SMTP failure may leave an account awaiting verification; once service recovers, resend the verification email instead of registering again.

Exchange Account API

Method and Path Authentication Purpose
POST /api/auth/register None; rate limited {username,password,email} creates an account awaiting verification; returns 202 and verification_required without issuing a session
POST /api/auth/login None; rate limited {email,password} issues a separate login session
GET /api/auth/me Operational token or login session Returns name, tenant_id, and role
POST /api/auth/logout Current login session Revokes the current session without revoking other clients
POST /api/auth/verify-email None; rate limited {username,code} consumes an email verification code
POST /api/auth/resend-verification None; rate limited {email} resends an email to the address awaiting verification
POST /api/auth/forgot-password None; rate limited {email} sends a recovery code to a verified address
POST /api/auth/reset-password None; rate limited {email,code,password} changes the password and revokes old sessions
POST /api/auth/email Current login session and account password {email,password} requests an email binding or change, effective after verification

A successful login returns username, email (if bound), tenant_id, token, and expires_in, with Cache-Control: no-store. Account passwords are salted and stored using Argon2id with default parameters. Account requests are limited to 20 per minute per TCP source IP and 10 per minute per username, with at most four concurrent password hashes. Client-supplied forwarded IPs are not trusted. Clients behind a reverse proxy share its IP quota; public deployments should configure additional rate limits at a trusted ingress.

Rust Exchange disables public registration by default. Enable it explicitly with EXCHANGE_ALLOW_REGISTRATION=true and configure SMTP; false disables new registrations while existing accounts can still log in.

Displaying a Six-Digit Code on the Agent

Android Remote Agent has the default address prefilled. Tap Connect with a six-digit code to display a code valid for 5 minutes. On an administrator CLI that is already logged in, run:

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

When retrieving the connection code, the CLI shows the device name, identity type, and suggested device ID. Verify that it is your device before approving. The default ID uses lowercase ASCII letters, digits, and ._- from the device name, removing spaces and other characters. It falls back to device when no usable characters remain, and is limited to 64 characters. If the ID is taken in the same workspace, a number is appended, such as realmev5, realmev51, and realmev52; the base ID is shortened when necessary. Exchange handles concurrent conflicts during creation, and the CLI prints the final allocated ID. Manually specified IDs also receive a numeric suffix on conflict. Android uses the system device name, falling back to the model; native Agents use the hostname. The Agent automatically receives a separate device token, stores it encrypted, and connects. Accessibility and screen control are not enabled automatically. Cancellation, expiry, or authentication failure preserves existing configuration; stop an Agent that is already connecting before pairing.

Adding Computers, Routers, and Linux Agents

Windows, macOS, OpenWrt, and Linux Agents can use either method without manually entering a device token:

Platform Method 1: Pairing Code / Device QR Code Method 2: Paste an Invitation
Windows / macOS (desktop --ui, launched by double-clicking) On the local page, choose Add this device → Get pairing code to display a six-digit code and QR code Paste into the local page's Invite text box and click Join with invite
OpenWrt (luci-app-remote) In LuCI, choose Services → Remote Management → Add Device → Get Pairing Code to display a six-digit code and QR code On the same page, paste into Method 2: Invitation Content and click Join
Linux / command line on any platform remote-agent pair displays a six-digit code and text QR code in the terminal remote-agent join '<邀请内容>', or remote-agent join - to read from standard input

Method 1: The Agent requests a six-digit code valid for 5 minutes and displays a QR code containing remote://pair?code=012345&server=wss%3A%2F%2Fexchange.ivjn.us (without credentials). Administrators may choose any of these options: Android Console Add device → Enter pairing code → Scan device QR code (scan with the camera, verify the server matches, then look up and approve the request); enter the six-digit code directly; use Web Console Approve pairing code to enter the code or paste remote://pair?...; or run remote-cli pair 012345 (which also accepts that URI). After approval, the Agent saves the token and connects automatically.

Method 2: In Android Console, an administrator chooses Add device → Add by scanning to generate a one-time invitation, then taps Copy invitation (paste on computer / router); alternatively, run remote-cli qr --device-id pc-01 --text. The invitation is the JSON embedded in the QR code ({"type":"remote-enrollment",...}), valid for 5 minutes and usable once. Contents of a device-token QR code (remote-cli device qr) are also accepted.

Storage locations and connection behavior:

  • Desktop UI: Writes to the current configuration file (by default %LOCALAPPDATA%\remote-agent\config.json / ~/.config/remote-agent/config.json) and connects immediately.
  • OpenWrt: Writes UCI remote-agent.main (server / device_id / token, enabled=1), then restarts the procd service.
  • Command line: Defaults to the desktop configuration path (UCI on OpenWrt). --config PATH selects a JSON file; --uci forces UCI storage. After saving, connects in the foreground (--no-connect saves only). Restart any existing service using that configuration.

Existing credentials are protected from replacement by default: the command line requires --force, while the desktop UI and LuCI ask for confirmation. Existing token-based methods (--device-id/--token, configuration files, and LuCI settings) remain available.

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

Native pairing requires wss://; ws:// is allowed only on loopback for testing. Android and Console pairing require HTTPS/WSS.

Console Six-Digit Code Login

On the Android Remote Console login page, tap Log in with a six-digit code. In Web Console (/console), select the Six-digit code tab in the login card and request a login code. An administrator can approve using either method:

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

Alternatively, in an authenticated Web Console workspace, click Approve pairing code (visible only to administrators; readonly operators have no such action). Enter the code shown on the other client's screen, verify its type and label, and approve.

By default, this creates a separate operator account instead of copying the administrator's token. An administrator may explicitly specify --role admin or --role readonly. After receiving authorization, Android Console validates the credentials again, saves them to SecureStore, and opens the device list. Web Console keeps operator credentials in the current page's memory (as with account/token login) and opens the device list. If cancelled or unsuccessful, existing account or token login remains available, and inputs subsequently entered for other login methods are preserved.

Scanning a CLI Code with Remote Agent

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

Generate a QR code from an authenticated administrator CLI. On the phone, open Remote Agent and tap Scan connection QR code, verify the server and device ID, then tap Connect. The QR code contains only a random 256-bit temporary ticket, valid for 5 minutes and bindable to one device; it contains no long-lived device token. After scanning, Exchange issues a device token that the phone stores in Keystore before connecting. The QR code must name a new device ID; it does not replace an existing device or rotate its token.

Claims from the same phone use a stable random claim_id, so retries after a connection failure can retrieve the same result; another claim_id cannot claim the ticket again. After saving, the phone acknowledges that the ticket may be cleared. Temporary tickets live in process memory and become invalid when Exchange restarts. A claim result already being retrieved is retained for up to another 5 minutes so the original client can retry.

Method and Path Authentication Purpose
POST /api/pairing/invite Administrator {device_id,name?} generates a ticket; limited to 10 per minute per administrator and 256 globally
POST /api/pairing/claim Bearer <ticket> {claim_id} returns separate device credentials; retries must reuse the same 32-character hexadecimal claim_id
POST /api/pairing/claim/complete Same as above Acknowledges successful storage and clears the ticket

Generating a Device QR Code

# 创建设备,同时在终端显示二维码
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 reads the device token through hidden input and also supports --token-stdin. Device QR codes contain device tokens only; operational tokens cannot be used. Output files refuse to overwrite existing files and contain credentials: protect them and delete them after use. QR codes require HTTPS/WSS.

Android Console displays a device QR code immediately after creating a device. In device details, Rotate token and generate QR code explains the disconnection impact and requests confirmation first. The server stores only token hashes and cannot recover an old QR code from an existing device record, so viewing a QR code never silently rotates the token.

Exchange Pairing API

Method and Path Authentication Purpose
POST /api/pairing/start None; rate limited Submit kind: device/operator and label; receive code, poll_token, expires_in: 300, and interval: 3
GET /api/pairing/{code} Administrator operational token Inspect request type, label, and remaining lifetime; returns no credentials
POST /api/pairing/approve Administrator operational token Submit code, kind, identity, optional name, and role to create a separate device or account
POST /api/pairing/poll Authorization: Bearer <poll_token> Returns pending or approved; approved results contain credentials.kind/identity/token
POST /api/pairing/complete Same as above Clears temporary authorization once the client has saved it; safe to call repeatedly
GET /ws/pairing None; rate limited Native Agent pairing WebSocket; receives code, then approved or expired events; sends text complete after saving

The six-digit code only lets an authenticated administrator locate and confirm a request; it cannot be used to poll or obtain credentials. The polling credential is a random 256-bit secret and is never placed in the URL. Duplicate approvals are rejected. Failed identity creation releases the approval reservation so it can be retried. The approved result can be read again with the same private polling credential, preventing permanent credential loss from a dropped network response. The client acknowledges clearing after saving.

Requests awaiting approval and approved results awaiting collection are each retained for at most 5 minutes. Without REDIS_URL, temporary sessions live only in the current Exchange process's memory; after a restart, request a new code, and route pairing requests to the same instance in multi-instance deployments. With Redis configured, pairing entries and rate-limit counters are stored in Redis (aligned with the 5-minute TTL), allowing requests to reach any instance with no session affinity required. Each actual TCP source IP may request up to 10 codes per minute; an administrator may perform up to 20 lookups or approvals per minute. Memory mode also has a global limit of 256 pending sessions. Proxy-forwarded client IPs are not trusted for rate limiting; users behind the same reverse proxy share its source quota. TTL expiry and scheduled cleanup remove expired data. Responses set Cache-Control: no-store, and neither pairing secrets nor issued plaintext tokens are logged.

Existing backend device IDs/accounts cannot be overwritten by pairing requests. Administrators should verify both the code and the requesting client; do not approve login codes supplied through someone else's manipulation.

Connection-code API: POST /api/pairing/start accepts optional device_name and returns that name. Administrator lookup GET /api/pairing/{code} returns device_name and the current workspace's suggested_device_id; looking it up does not reserve the ID. The device identity in POST /api/pairing/approve may be omitted. Success returns 200 with {identity} (the final ID, without a token). Native WebSocket /ws/pairing?device_name=... supports URL-encoded device names.