lib/openagents/scv/driver_login_attempt.ex

main at 58e6347eeb72 · 2 KB

defmodule OpenAgents.SCV.DriverLoginAttempt do
  @moduledoc "Durable, credential-free state for one SCV Codex device login."

  use Ecto.Schema
  import Ecto.Changeset

  alias OpenAgents.Accounts.User
  alias OpenAgents.SCV.DriverAccount

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  @timestamps_opts [type: :utc_datetime_usec]

  schema "scv_driver_login_attempts" do
    field :login_id, :string
    field :status, :string, default: "starting"
    field :verification_url, :string
    field :user_code_digest, :binary, redact: true
    field :expires_at, :utc_datetime_usec
    field :completed_at, :utc_datetime_usec
    field :failure_code, :string

    belongs_to :account, DriverAccount
    belongs_to :operator, User

    timestamps()
  end

  @type t :: %__MODULE__{}

  @doc false
  def create_changeset(attempt, attributes) do
    attempt
    |> cast(attributes, [:account_id, :operator_id, :expires_at])
    |> validate_required([:account_id, :operator_id, :expires_at])
    |> put_change(:status, "starting")
    |> foreign_key_constraint(:account_id)
    |> foreign_key_constraint(:operator_id)
    |> check_constraint(:status, name: :scv_driver_login_attempts_status_check)
  end

  @doc false
  def waiting_changeset(attempt, attributes) do
    attempt
    |> cast(attributes, [:login_id, :verification_url, :user_code_digest])
    |> validate_required([:login_id, :verification_url, :user_code_digest])
    |> validate_change(:verification_url, &validate_verification_url/2)
    |> put_change(:status, "waiting")
    |> unique_constraint(:login_id)
    |> check_constraint(:status, name: :scv_driver_login_attempts_status_check)
  end

  @doc false
  def terminal_changeset(attempt, status, failure_code \\ nil)
      when status in ["succeeded", "failed", "cancelled", "expired"] do
    attempt
    |> change(
      status: status,
      failure_code: failure_code,
      completed_at: DateTime.utc_now()
    )
    |> check_constraint(:status, name: :scv_driver_login_attempts_status_check)
  end

  defp validate_verification_url(:verification_url, value) do
    case URI.new(value) do
      {:ok, %URI{scheme: "https", host: "auth.openai.com", path: "/codex/device"}} -> []
      _invalid -> [verification_url: "must be the Codex device verification URL"]
    end
  end
end