priv/repo/migrations/20260825140544_add_notification_email_address_to_users.exs

58e6347eeb72 · 1 KB

defmodule OpenAgents.Repo.Migrations.AddNotificationEmailAddressToUsers do
  @moduledoc """
  An address to send to, and the proof that its owner asked for it.

  GitHub OAuth is not asked for `user:email` here. The address is typed into
  the notification settings by the person who wants mail, and it is inert until
  a code sent to that mailbox comes back. So the account carries four facts:
  the address, when it was verified, the digest of the code currently
  outstanding, and when that code went out.

  The code is stored as a SHA-256 digest, never as plaintext, for the same
  reason a token is: a database read must not be enough to claim somebody
  else's mailbox. `notification_email_code_attempts` bounds guessing at the
  address, since a short code is guessable if the guesses are free.

  The check constraint is the durable half of the gate. `verified_at` cannot be
  set without an address, and a code cannot be outstanding without one either,
  so no row can claim a verified mailbox it does not name. The application
  refuses first; this refuses when the application is wrong.

  Expand-only: every column is nullable or defaulted, so a node running the
  previous release keeps writing rows this migration accepts.
  """

  use Ecto.Migration

  def change do
    alter table(:users) do
      add :notification_email, :text
      add :notification_email_verified_at, :utc_datetime_usec
      add :notification_email_code_digest, :binary
      add :notification_email_code_sent_at, :utc_datetime_usec
      add :notification_email_code_attempts, :integer, null: false, default: 0
    end

    create constraint(:users, :users_notification_email_state_check,
             check: """
             notification_email IS NOT NULL
               OR (notification_email_verified_at IS NULL
                   AND notification_email_code_digest IS NULL
                   AND notification_email_code_sent_at IS NULL)
             """
           )
  end
end