Skip to content

feat(server): per-device approval and separate admin page - #15

Merged
loyalpartner merged 1 commit into
masterfrom
feature/device-approval
Oct 15, 2026
Merged

loyalpartner merged 1 commit into
masterfrom
feature/device-approval

Conversation

@loyalpartner

Copy link
Copy Markdown
Owner

Closes #13

Adds per-device approval. Instead of a global "allow new registrations" switch, each browser install is approved individually: blocking only new users would not stop someone sending requests with an email that is already registered.

What changes

  • Device identity: Chromium appends client_id=<cache_guid> to every sync request URL, so devices are recognised from the first GET_UPDATES.
  • Approval switch on the admin page. When on, unknown devices are recorded as pending and get TRANSIENT_ERROR; the browser keeps retrying and resumes on its own once approved, no restart. When off, devices are recorded and auto-approved, so turning it on later doesn't lock out devices already in use. Rejected devices stay blocked either way.
  • The check runs before the user lookup, so an unapproved device can't make the server allocate a user.
  • Admin page on its own listener, SELFSYNC_ADMIN_ADDR (default 127.0.0.1:8081). Server-rendered with askama, no JavaScript, English and Chinese. Shows the device name from synced DeviceInfo once a device has synced, else a label parsed from the sync User-Agent (Chrome 141 · Windows).
  • Admin requests: cross-site POSTs are refused (Sec-Fetch-Site, falling back to Origin vs Host); on a loopback bind a non-loopback Host is refused (DNS rebinding).
  • New devices and settings tables (new migration, existing ones untouched).
  • Lazycat: opening the app shows the admin page behind Lazycat login; only the sync paths are public. Adds lzc-build.dev.yml to build the image on the box under a separate dev package.

Design notes: docs/device-approval.md.

Breaking change

GET / on the sync port is gone; the page moved to the admin port. Docker exposes it as 127.0.0.1:8081 in docker-compose.yml.

Testing

  • cargo test (48 tests, incl. admission against a real SQLite DB), cargo clippy --all-targets -- -D warnings, cargo fmt --check.
  • End to end against the release binary with crafted sync requests: switch off/on, approve, revoke, unknown email gets no user row, cross-site POST and forged Host get 403.

Not yet verified with real browsers:

  • whether Edge and Brave send client_id
  • how long the client backoff takes to resume after approval
  • device names from DeviceInfo
  • deploying the Lazycat package

🤖 Generated with Claude Code

- 按 URL 的 client_id 识别设备,审核开关打开后未批准设备收到 TRANSIENT_ERROR
- 判断放在建用户之前;开关关闭时记录设备并自动批准
- 管理页挪到 SELFSYNC_ADMIN_ADDR(默认 127.0.0.1:8081),askama 渲染,中英文
- 管理页拒绝跨站 POST,回环监听时拒绝非本机 Host;同步端口去掉 GET /
- 懒猫:打开应用进管理页,仅同步路径免登录;加 dev 构建配置
- 设计见 docs/device-approval.md
@loyalpartner
loyalpartner merged commit 581c980 into master Oct 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to disable device registration

1 participant