Skip to main content

lib/ash_authentication/oauth2_server/refresh_token_resource.ex

# SPDX-FileCopyrightText: 2026 ash_authentication_oauth2_server contributors <https://github.com/ash-project/ash_authentication_oauth2_server/graphs/contributors>
#
# SPDX-License-Identifier: MIT

defmodule AshAuthentication.Oauth2Server.RefreshTokenResource do
  @default_expunge_interval_hrs 12
  @default_grace_seconds 86_400

  @dsl [
    %Spark.Dsl.Section{
      name: :oauth2_server,
      describe: "Configuration for an OAuth2 refresh-token resource",
      schema: [
        expunge_expired_action_name: [
          type: :atom,
          doc:
            "The name of the auto-generated destroy action that removes expired/rotated/revoked rows.",
          default: :expunge_expired
        ],
        expunge_interval: [
          type: :pos_integer,
          doc:
            "How often (in hours) to scan this resource for rows that have expired and can be removed.",
          default: @default_expunge_interval_hrs
        ],
        revoked_grace: [
          type: :pos_integer,
          doc: "How long (in seconds) to keep revoked refresh-token rows before expunging them.",
          default: @default_grace_seconds
        ],
        rotated_grace: [
          type: :pos_integer,
          doc: "How long (in seconds) to keep rotated refresh-token rows before expunging them.",
          default: @default_grace_seconds
        ]
      ]
    }
  ]

  @moduledoc """
  Resource extension for OAuth 2.1 refresh-token rows.

  Verifies, at compile time, that the resource conforms to the
  contract the `Token` core depends on for race-safe rotation
  (writable `:id`, required attributes, a `:rotate` action carrying
  `AshAuthentication.Oauth2Server.Changes.RotateRefreshToken`).

  Adds an auto-generated `:expunge_expired` destroy action and
  exposes configuration for the
  `AshAuthentication.Oauth2Server.Expunger` GenServer, which
  periodically removes:

    * rows whose `expires_at` has passed
    * rows whose `revoked_at` is older than `revoked_grace`
    * rows whose `rotated_at` is older than `rotated_grace`

  ## Usage

      use Ash.Resource,
        extensions: [AshAuthentication.Oauth2Server.RefreshTokenResource],
        ...

      oauth2_server do
        expunge_interval 12
        revoked_grace 86_400
        rotated_grace 86_400
      end

  ## Removing expired records

  Add `AshAuthentication.Oauth2Server.Supervisor` to your application
  supervision tree; it starts the expunger which scans on each
  resource's configured interval.
  """

  alias AshAuthentication.Oauth2Server.RefreshTokenResource

  use Spark.Dsl.Extension,
    sections: @dsl,
    transformers: [RefreshTokenResource.Transformer],
    verifiers: [RefreshTokenResource.Verifier]

  @doc """
  Bulk-destroy refresh-token rows that have expired, or whose
  `revoked_at` / `rotated_at` is older than the configured grace.
  """
  @spec expunge_expired(Ash.Resource.t(), keyword) :: :ok | {:error, any}
  defdelegate expunge_expired(resource, opts \\ []), to: RefreshTokenResource.Actions
end