From 752b160c7c5e671e0db8f402a52fac27bb85ab06 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 1 Oct 2026 11:47:39 +0200 Subject: fix(hub): a stranger who knows your name locks only browsers you never used A sign-in from a browser that presented no token is answered with one (known_browser, kept hashed, twenty per account); a later sign-in presenting it counts failures on its own row, which nobody else can spend. Passphrase checks inside an open session (change, e-mail, deletion, device, pepper) count on the account's own row, so a locked name no longer stops its owner there either; /me reports that row. Reset and erasure forget the browsers (F-15). Co-Authored-By: Claude Opus 5.5 --- .../versions/d4e5f6a7b8ca_known_browsers.py | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d4e5f6a7b8ca_known_browsers.py (limited to 'packages/meshbay-hub/src/meshbay_hub/db/migrations') diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d4e5f6a7b8ca_known_browsers.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d4e5f6a7b8ca_known_browsers.py new file mode 100644 index 0000000..aaad5c7 --- /dev/null +++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/d4e5f6a7b8ca_known_browsers.py @@ -0,0 +1,32 @@ +"""browsers an account has signed in from, each with its own failure counter + +Revision ID: d4e5f6a7b8ca +Revises: c3d4e5f6a7b9 +""" + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +revision: str = "d4e5f6a7b8ca" +down_revision: str | Sequence[str] | None = "c3d4e5f6a7b9" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + op.create_table( + "known_browsers", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("user_id", sa.String(36), sa.ForeignKey("users.id"), nullable=False), + sa.Column("token_hash", sa.String(64), nullable=False, unique=True), + sa.Column("created_at", sa.DateTime(timezone=True)), + sa.Column("last_used_at", sa.DateTime(timezone=True)), + ) + op.create_index("ix_known_browsers_user_id", "known_browsers", ["user_id"]) + + +def downgrade() -> None: + op.drop_index("ix_known_browsers_user_id", table_name="known_browsers") + op.drop_table("known_browsers") -- cgit v1.2.3