Schema for DB Sync persistent connections between PhoenixKit instances.
Connections allow two PhoenixKit sites to establish a permanent relationship for data synchronization, replacing ephemeral session codes with token-based authentication.
Direction
Each connection has a direction:
"sender"- This site will send data (configured on the data-sharing site)"receiver"- This site will receive data (configured on the data-receiving site)
Approval Modes (Sender-side)
Stored and read by nothing: no transfer is held for approval, so every
mode behaves as "auto_approve". (Approving the connection itself is
separate: a new connection is "pending" until an admin of this site
approves it.)
"auto_approve"- All transfers are automatically approved"require_approval"- Each transfer needs manual approval"per_table"- Tables inauto_approve_tablesdon't need approval, others do
Status Flow
pending → active → suspended → revokedA connection past expires_at or its limits is refused on every request
(Connections.authorize_request/3); its stored status stays as it was.
"expired" is a valid status that nothing in this module sets.
Security Features
Checked on every request by Connections.authorize_request/3:
- Token-based authentication (auth_token_hash)
- Optional download password
- IP whitelist
- Time-of-day restrictions
- Download and record limits
- Expiration date
- Requests per minute (
rate_limit_requests_per_minute,0for no limit;PhoenixKitSync.RateLimit)
Usage Examples
# Create a sender connection
{:ok, conn} = Connections.create_connection(%{
name: "Production Receiver",
direction: "sender",
site_url: "https://receiver.example.com",
approval_mode: "auto_approve"
})
# Approve a pending connection
{:ok, conn} = Connections.approve_connection(conn, admin_user_uuid)
# Suspend a connection
{:ok, conn} = Connections.suspend_connection(conn, admin_user_uuid, "Security audit")
Summary
Types
A connection row. auth_token and download_password are virtual: set
on the way in, stored only as their hashes. A *_user association is
Ecto.Association.NotLoaded until preloaded.
Functions
Checks if a connection is currently active and within limits.
Changeset for approving a connection.
Creates a changeset for connection creation.
Checks if a connection has expired.
Generates a secure random token for connection authentication.
Checks if an IP address is in the whitelist.
The bounds of max_records_per_request, the most rows one request is
answered with: the changesets validate against them and the connection
form offers them.
The form two spellings of one site's URL share, for comparing them: scheme and host in lower case (the host without a trailing dot), the scheme's default port left out, trailing slashes dropped. The path keeps its case, and a fragment is ignored.
Changeset for reactivating a suspended connection.
Changeset for a status the remote site reports through the API.
Whether the connection's suspension was reported by the remote site rather than set by an admin here.
Changeset for revoking a connection.
Changeset for updating connection settings.
Changeset for updating connection statistics.
Changeset for suspending a connection.
Checks if a table is allowed to be accessed.
Verifies an auth token against the stored hash.
Verifies a download password against the stored hash.
Checks if current time is within allowed hours.
Checks if a connection is within download limits.
Checks if a connection is within record limits.
Types
@type t() :: %PhoenixKitSync.Connection{ __meta__: term(), allowed_hours_end: integer() | nil, allowed_hours_start: integer() | nil, allowed_tables: [String.t()] | nil, approval_mode: String.t() | nil, approved_at: DateTime.t() | nil, approved_by_user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t() | nil, approved_by_uuid: UUIDv7.t() | nil, auth_token: String.t() | nil, auth_token_hash: String.t() | nil, auto_approve_tables: [String.t()] | nil, auto_sync_enabled: boolean() | nil, auto_sync_interval_minutes: integer() | nil, auto_sync_tables: [String.t()] | nil, created_by_user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t() | nil, created_by_uuid: UUIDv7.t() | nil, default_conflict_strategy: String.t() | nil, direction: String.t() | nil, download_password: String.t() | nil, download_password_hash: String.t() | nil, downloads_used: integer() | nil, excluded_tables: [String.t()] | nil, expires_at: DateTime.t() | nil, inserted_at: DateTime.t() | nil, ip_whitelist: [String.t()] | nil, last_connected_at: DateTime.t() | nil, last_transfer_at: DateTime.t() | nil, max_downloads: integer() | nil, max_records_per_request: integer() | nil, max_records_total: integer() | nil, metadata: map() | nil, name: String.t() | nil, rate_limit_requests_per_minute: integer() | nil, records_downloaded: integer() | nil, revoked_at: DateTime.t() | nil, revoked_by_user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t() | nil, revoked_by_uuid: UUIDv7.t() | nil, revoked_reason: String.t() | nil, site_url: String.t() | nil, status: String.t() | nil, suspended_at: DateTime.t() | nil, suspended_by_user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t() | nil, suspended_by_uuid: UUIDv7.t() | nil, suspended_reason: String.t() | nil, total_bytes_transferred: integer() | nil, total_records_transferred: integer() | nil, total_transfers: integer() | nil, updated_at: DateTime.t() | nil, uuid: UUIDv7.t() | nil }
A connection row. auth_token and download_password are virtual: set
on the way in, stored only as their hashes. A *_user association is
Ecto.Association.NotLoaded until preloaded.
Functions
Checks if a connection is currently active and within limits.
@spec approve_changeset(t(), String.t() | map() | nil) :: Ecto.Changeset.t()
Changeset for approving a connection.
@spec changeset(t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
Creates a changeset for connection creation.
Checks if a connection has expired.
@spec generate_auth_token() :: String.t()
Generates a secure random token for connection authentication.
Checks if an IP address is in the whitelist.
@spec max_records_per_request_range() :: Range.t()
The bounds of max_records_per_request, the most rows one request is
answered with: the changesets validate against them and the connection
form offers them.
The form two spellings of one site's URL share, for comparing them: scheme and host in lower case (the host without a trailing dot), the scheme's default port left out, trailing slashes dropped. The path keeps its case, and a fragment is ignored.
Connections store site_url as it was given; this is what registration
and the Connections.find_by_site_url* lookups compare, so
https://Example.com:443/ and https://example.com are one site. Text
that is not a URL with a scheme and a host is returned trimmed.
@spec reactivate_changeset(t()) :: Ecto.Changeset.t()
Changeset for reactivating a suspended connection.
@spec remote_status_changeset(t(), String.t()) :: {:ok, Ecto.Changeset.t()} | :unchanged | {:error, :status_change_not_allowed}
Changeset for a status the remote site reports through the API.
The peer is not trusted with this site's decisions, so only these moves
exist, and only on a "receiver" row (the sender is the side that
notifies):
activetosuspendedorrevokedsuspendedtoactiveorrevoked, when the suspension itself came from the peer (a suspension set here stays until an admin here lifts it)
A pending, revoked or expired row never changes, and neither does a
"sender" row: those statuses decide what the peer may read.
Returns {:ok, changeset}, :unchanged when the row already has that
status, or {:error, :status_change_not_allowed}.
Whether the connection's suspension was reported by the remote site rather than set by an admin here.
@spec revoke_changeset(t(), String.t() | map() | nil, String.t() | nil) :: Ecto.Changeset.t()
Changeset for revoking a connection.
@spec settings_changeset(t(), map()) :: Ecto.Changeset.t()
Changeset for updating connection settings.
@spec stats_changeset(t(), map()) :: Ecto.Changeset.t()
Changeset for updating connection statistics.
@spec suspend_changeset(t(), String.t() | map() | nil, String.t() | nil) :: Ecto.Changeset.t()
Changeset for suspending a connection.
Checks if a table is allowed to be accessed.
Verifies an auth token against the stored hash.
Verifies a download password against the stored hash.
Checks if current time is within allowed hours.
Checks if a connection is within download limits.
Checks if a connection is within record limits.