summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/roster.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
commit28c88908b88c58cba4de08710f6bb268ede02d57 (patch)
tree7e55f276ee0a8a25f6175f9ef81ebb845d1c218d /packages/meshbay-node/src/meshbay_node/roster.py
parent51705056a578d2649a989bf14a056fbcfae58a6b (diff)
downloadmeshbay-28c88908b88c58cba4de08710f6bb268ede02d57.tar.gz
feat(node): accept a device link request before the proof (MNP 6.3)
A new device of a known account holds no identity the node pinned, so it cannot complete a handshake until a pinned device approves it: it now files device_add_request in the pre-proof window, as the desktop app's case already required. The request stays inert until countersigned; at most 10 are pending per account, the oldest dropped. A 6.2 node answers it "Handshake required", which a client reads as "cannot link here". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/roster.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/roster.py13
1 files changed, 13 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/roster.py b/packages/meshbay-node/src/meshbay_node/roster.py
index 9336e38..9123763 100644
--- a/packages/meshbay-node/src/meshbay_node/roster.py
+++ b/packages/meshbay-node/src/meshbay_node/roster.py
@@ -327,6 +327,7 @@ class Roster:
# keypair bundle can add one — so the answer to "how many" is visibility and
# a ceiling, not cryptography.
MAX_DEVICES_PER_USER = 5
+ MAX_PENDING_DEVICE_REQUESTS = 10
async def pin_identity(
self,
@@ -522,6 +523,18 @@ class Roster:
"""
assert self._db
expires = _iso_in(ttl)
+ # Filing needs only an authorized token (it is served before the proof),
+ # so the table is bounded per account: expired requests go, and past the
+ # cap the oldest makes room. A person files one per new device, five at
+ # most, and a retry or two each; a request pushed out costs a new code.
+ await self._db.execute(
+ "DELETE FROM device_requests WHERE user_id = ? AND expires_at <= ?",
+ (user_id, _now()))
+ await self._db.execute(
+ "DELETE FROM device_requests WHERE code_hash IN ("
+ " SELECT code_hash FROM device_requests WHERE user_id = ?"
+ " ORDER BY created_at DESC, rowid DESC LIMIT -1 OFFSET ?)",
+ (user_id, self.MAX_PENDING_DEVICE_REQUESTS - 1))
await self._db.execute(
"INSERT OR REPLACE INTO device_requests "
"(code_hash, user_id, username, pk_ed25519, pk_x25519, created_at, "