"""app passwords: one password per mail app (SPEC App. D §D.5) Mail apps (Thunderbird, a phone's Mail app) will sign in to the user's hosted mailbox over IMAP/SMTP, where no 2FA code can be asked for. Each app gets its own generated password instead, which can be revoked on its own; the main password with 2FA stays for the webmail. Stored as argon2id hashes, the form the mail server's SQL directory reads (§D.3). Revision ID: 0024 Revises: 0023 Create Date: 2026-10-09 """ from __future__ import annotations from collections.abc import Sequence from alembic import op revision: str = "0024" down_revision: str | None = "0023" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.execute( """ CREATE TABLE user_app_passwords ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, name text NOT NULL, password_hash text NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), last_used_at timestamptz ) """ ) op.execute("CREATE INDEX ix_user_app_passwords_user ON user_app_passwords (user_id)") def downgrade() -> None: op.execute("DROP TABLE IF EXISTS user_app_passwords")