sync: unlinking a device now revokes its token on the server (issue 2110)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 40s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m13s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m9s
Desktop (Tauri) / Update manifest (push) Successful in 4s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 40s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m13s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m9s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Unlink was local-only. It cleared the server URL, token and cursor from the device, and left the bearer token valid on the server indefinitely — so someone who unlinked because the laptop was being sold or handed on believed they had revoked access when they hadn't. The blocker was identification, not intent: a token pasted from the web app never carried a device id, and /api/auth/me describes the user, not the device row, so DELETE /devices/<id> could only ever have worked for one of the two ways this app can be linked. DELETE /api/auth/devices/self keys off the token in the Authorization header instead, which the caller always holds — one route that works for both paths, owner-scoped like the rest, and no local schema change. Unlinking is never blocked on the network. Wanting to stop syncing is a local decision, so the revoke is attempted first, its outcome carried back, and the link cleared either way. When the token survives — server unreachable, or older than the route — the Sync screen says so in place, with where to revoke it. A toast would have been the wrong shape for that: it disappears, and this is exactly what someone returns to the screen to check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,16 @@ shown once at creation.
|
||||
device}`. They paste it into the native app.
|
||||
- **Manage:** `GET /api/auth/devices` (list), `DELETE /api/auth/devices/<id>`
|
||||
(revoke). A revoked token stops authenticating immediately.
|
||||
- **Self-revoke:** `DELETE /api/auth/devices/self` retires the token presented in
|
||||
the `Authorization` header. This is what a native client calls when the user
|
||||
unlinks. It exists because a client can't use the id-keyed route: a token pasted
|
||||
from the web app arrives without a device id, and `/api/auth/me` describes the
|
||||
user, not the device row. A caller authenticated by session cookie gets `400` —
|
||||
it holds no device token, so there is nothing for it to mean.
|
||||
|
||||
Unlinking is never blocked on this call. If the server is unreachable or too old
|
||||
to have the route, the client still unlinks locally and tells the user the token
|
||||
is still live and where to revoke it.
|
||||
|
||||
Every authenticated request (sync or otherwise) accepts the bearer token in
|
||||
place of the session cookie.
|
||||
|
||||
Reference in New Issue
Block a user