Release 0.11.0: trust device for 30 days
This commit is contained in:
@@ -26,6 +26,7 @@ from . import (
|
||||
api_prs,
|
||||
auth,
|
||||
db,
|
||||
device_trust as device_trust_mod,
|
||||
docs as docs_mod,
|
||||
entry as entry_mod,
|
||||
cache,
|
||||
@@ -252,6 +253,68 @@ def make_router(
|
||||
notify.fan_out_new_beta_request(requester_user_id=user.user_id)
|
||||
return {"ok": True}
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# v0.11.0: trust device for 30 days (§6.2, roadmap item #9).
|
||||
#
|
||||
# The mint path lives on the OAuth router (issuing the cookie is
|
||||
# coupled to OTC/passcode verify). This module owns the read/revoke
|
||||
# surface the /settings/devices page calls.
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@router.get("/api/auth/me/devices")
|
||||
async def list_my_devices(request: Request) -> dict[str, Any]:
|
||||
"""Active device-trust rows for the signed-in user.
|
||||
|
||||
Active = not revoked, not expired. The current request's
|
||||
device (if any) is *not* singled out here — the surface
|
||||
shows the same row shape for every device so the user can
|
||||
revoke any of them without the page leaking which row
|
||||
carries the cookie they're using right now.
|
||||
"""
|
||||
user = auth.require_user(request)
|
||||
rows = device_trust_mod.list_for_user(user.user_id)
|
||||
return {
|
||||
"items": [
|
||||
{
|
||||
"id": r.id,
|
||||
"created_at": r.created_at,
|
||||
"expires_at": r.expires_at,
|
||||
"last_seen_at": r.last_seen_at,
|
||||
"user_agent": r.user_agent,
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
}
|
||||
|
||||
@router.delete("/api/auth/me/devices/{device_id}")
|
||||
async def revoke_my_device(device_id: int, request: Request) -> dict[str, Any]:
|
||||
"""Revoke a single device-trust row for the signed-in user.
|
||||
|
||||
The user-id scope is enforced in SQL so a hostile client
|
||||
cannot revoke another user's row by guessing ids. A row that
|
||||
doesn't exist, doesn't belong to this user, or is already
|
||||
revoked reads as 404 — the wrong-vs-already-revoked
|
||||
distinction would only help a probing client enumerate ids.
|
||||
"""
|
||||
user = auth.require_user(request)
|
||||
ok = device_trust_mod.revoke(user.user_id, device_id)
|
||||
if not ok:
|
||||
raise HTTPException(404, "Device not found")
|
||||
return {"ok": True}
|
||||
|
||||
@router.delete("/api/auth/me/devices")
|
||||
async def revoke_all_my_devices(request: Request) -> dict[str, Any]:
|
||||
"""Revoke every active device-trust row for the signed-in user.
|
||||
|
||||
The user's current request stays authenticated via its
|
||||
session cookie; the device-trust cookie carried on the
|
||||
current device is also revoked, but `rfc_session` keeps the
|
||||
request flow alive until sign-out / expiry.
|
||||
"""
|
||||
user = auth.require_user(request)
|
||||
count = device_trust_mod.revoke_all(user.user_id)
|
||||
return {"ok": True, "revoked": count}
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# §7: the catalog
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user