aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/app.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-31 17:19:17 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-31 17:19:17 +0200
commitc6fd7ea89b6e0a96eb1d81989de891b4768b1044 (patch)
tree12e869044c80c889f83b588210cc0ac500cd7a6a /packages/meshbay-hub/src/meshbay_hub/app.py
parentf4c6628c8e85513d9fd110ead95682368a15a0fd (diff)
downloadmeshbay-c6fd7ea89b6e0a96eb1d81989de891b4768b1044.tar.gz
feat: email verification for registration, email change, and invitations
Registration now creates a pending account and sends a 6-digit code via email; the account activates only after verification. Email changes on the profile page follow the same flow. Group invitations send a notification email to the invitee (without revealing their address to the inviter) containing the invite code and hub link. Backend: blind HMAC-SHA256 email index for uniqueness without decryption, mail.py for localhost Postfix delivery, verification endpoints, cleanup of expired codes and stale pending accounts, startup backfill of email_hash for existing users. Frontend: 3-phase register page, inline email change verification on profile, invite-notify call with status display. All 10 locales updated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/app.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/app.py36
1 files changed, 36 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/app.py b/packages/meshbay-hub/src/meshbay_hub/app.py
index 423ad03..1726ff4 100644
--- a/packages/meshbay-hub/src/meshbay_hub/app.py
+++ b/packages/meshbay-hub/src/meshbay_hub/app.py
@@ -56,6 +56,37 @@ async def _sync_admin_roles(admin_usernames: list[str]) -> None:
await session.commit()
+async def _backfill_email_hashes() -> None:
+ """One-time backfill: compute email_hash for users that don't have one yet."""
+ import logging
+
+ from sqlalchemy import select
+
+ from meshbay_hub.auth import decrypt_email, hash_email_blind
+ from meshbay_hub.db.engine import get_session_factory
+ from meshbay_hub.db.models import User
+
+ log = logging.getLogger(__name__)
+ factory = get_session_factory()
+ async with factory() as session:
+ result = await session.execute(
+ select(User).where(User.email_hash.is_(None), User.email.isnot(None))
+ )
+ users = list(result.scalars().all())
+ if not users:
+ return
+ count = 0
+ for user in users:
+ try:
+ plain = decrypt_email(user.email)
+ user.email_hash = hash_email_blind(plain)
+ count += 1
+ except Exception:
+ log.warning("Could not backfill email_hash for user %s", user.id)
+ await session.commit()
+ log.info("Backfilled email_hash for %d users", count)
+
+
def create_app(cfg: HubConfig | None = None) -> FastAPI:
from meshbay_hub.config import load_config
if cfg is None:
@@ -73,6 +104,11 @@ def create_app(cfg: HubConfig | None = None) -> FastAPI:
users_set_config(cfg)
set_admin_usernames(cfg.identity.admin_usernames)
+ await _backfill_email_hashes()
+
+ from meshbay_hub import mail as _mail
+ _mail.configure(cfg.identity.id)
+
if cfg.identity.admin_usernames:
await _sync_admin_roles(cfg.identity.admin_usernames)