diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-31 17:19:17 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-31 17:19:17 +0200 |
| commit | c6fd7ea89b6e0a96eb1d81989de891b4768b1044 (patch) | |
| tree | 12e869044c80c889f83b588210cc0ac500cd7a6a /packages/meshbay-hub/src/meshbay_hub/app.py | |
| parent | f4c6628c8e85513d9fd110ead95682368a15a0fd (diff) | |
| download | meshbay-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.py | 36 |
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) |