diff --git a/.ci/functional_test.sh b/.ci/functional_test.sh
deleted file mode 100755
index db37b8256..000000000
--- a/.ci/functional_test.sh
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/bin/bash
-set -ex
-# This script should be run from the app root
-
-function print_logs() {
- sudo cat /var/log/firezone/nginx/current
- sudo cat /var/log/firezone/postgresql/current
- sudo cat /var/log/firezone/phoenix/current
- sudo cat /var/log/firezone/wireguard/current
-}
-
-trap print_logs EXIT
-
-# Disable telemetry
-sudo mkdir -p /opt/firezone/
-sudo touch /opt/firezone/.disable-telemetry
-
-if type rpm > /dev/null; then
- sudo -E rpm -i omnibus/pkg/firezone*.rpm
-elif type dpkg > /dev/null; then
- sudo -E dpkg -i omnibus/pkg/firezone*.deb
-else
- echo 'Neither rpm nor dpkg found'
- exit 1
-fi
-
-# Fixes setcap not found on centos 7
-PATH=/usr/sbin/:$PATH
-
-# Disable connectivity checks
-conf="/opt/firezone/embedded/cookbooks/firezone/attributes/default.rb"
-search="default\['firezone']\['connectivity_checks']\['enabled'] = true"
-replace="default['firezone']['connectivity_checks']['enabled'] = false"
-sudo -E sed -i "s/$search/$replace/" $conf
-
-# Disable telemetry
-search="default\['firezone']\['telemetry']\['enabled'] = true"
-search="default['firezone']['telemetry']['enabled'] = false"
-sudo -E sed -i "s/$search/$replace/" $conf
-
-# Bootstrap config
-sudo -E firezone-ctl reconfigure
-
-# Wait for app to fully boot
-sleep 5
-
-# Helpful for debugging
-print_logs
-
-# Create admin; requires application to be up
-sudo -E firezone-ctl create-or-reset-admin
-
-# XXX: Add more commands here to test
-
-echo "Trying to load homepage"
-page=$(curl -L -i -vvv -k https://localhost)
-echo $page
-
-echo "Testing for sign in button"
-echo $page | grep 'Sign in with email'
-
-echo "Testing telemetry_id survives reconfigures"
-tid1=`sudo cat /var/opt/firezone/cache/telemetry_id`
-sudo firezone-ctl reconfigure
-tid2=`sudo cat /var/opt/firezone/cache/telemetry_id`
-
-if [ "$tid1" = "$tid2" ]; then
- echo "telemetry_ids match!"
-else
- echo "telemetry_ids differ:"
- echo $tid1
- echo $tid2
- exit 1
-fi
-
-fz_bin="/opt/firezone/embedded/service/firezone/bin/firezone"
-ok_res=":ok"
-
-echo "Testing FzVpn.Interface module works with WireGuard"
-set_interface=`sudo $fz_bin rpc "IO.inspect(FzVpn.Interface.set(\"wg-fz-test\", %{}))"`
-del_interface=`sudo $fz_bin rpc "IO.inspect(FzVpn.Interface.delete(\"wg-fz-test\"))"`
-
-if [[ "$set_interface" != $ok_res || "$del_interface" != $ok_res ]]; then
- echo "WireGuard test failed!"
- exit 1
-fi
-
-echo "Testing Firewall Rules"
-user_id="5" # Picking a high enough user_id so there is no overlap
-device="%{ip: \"10.0.0.1\", ip6: \"fd00::3:2:1\", user_id: $user_id}"
-rule="%{destination: \"10.0.0.2\", user_id: $user_id, action: :drop, port_type: nil, port_range: nil}"
-add_user=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.add_user($user_id))"`
-add_device=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.add_device($device))"`
-add_rule=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.add_rule($rule))"`
-del_rule=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.delete_rule($rule))"`
-del_device=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.delete_device($device))"`
-del_user=`sudo $fz_bin rpc "IO.inspect(FzWall.CLI.Live.delete_user($user_id))"`
-
-if [[ "$add_user" != $ok_res || "$add_device" != $ok_res || "$add_rule" != '""' || "$del_rule" != '""' || "$del_device" != $ok_res || "$del_user" != $ok_res ]]; then
- echo "Firewall test failed!"
- exit 1
-fi
diff --git a/.dockerignore b/.dockerignore
index 14996481d..2c46d2905 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,6 +1,8 @@
apps/web/assets/node_modules
apps/web/priv/static/dist
apps/web/priv/cert
+apps/api/priv/static/dist
+apps/api/priv/cert
_build
**/cover
docs
diff --git a/.gitignore b/.gitignore
index 237681167..c49b1d5ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,8 +66,5 @@ apps/*/screenshots
# WG configs generated in acceptance tests
*.conf
-# Auto generated private key
-apps/web/priv/wg_dev_private_key
-
# Uploads
apps/web/priv/static/uploads
diff --git a/Dockerfile.prod b/Dockerfile.prod
index a7f5fc64e..d750d8191 100644
--- a/Dockerfile.prod
+++ b/Dockerfile.prod
@@ -2,6 +2,7 @@ ARG ELIXIR_VERSION=1.14.3
ARG OTP_VERSION=25.2.1
ARG ALPINE_VERSION=3.16.3
+ARG APP_NAME="web"
ARG BUILDER_IMAGE="firezone/elixir:${ELIXIR_VERSION}-otp-${OTP_VERSION}"
ARG RUNNER_IMAGE="alpine:${ALPINE_VERSION}"
@@ -42,12 +43,11 @@ COPY apps apps
ARG VERSION=0.0.0-docker
ENV VERSION=$VERSION
-# compile assets
-RUN mix tailwind.install --if-missing \
- && mix esbuild.install --if-missing \
- && mix tailwind default --minify \
- && mix esbuild default --minify \
- && mix phx.digest
+# Install and compile assets
+RUN cd apps/web \
+ && mix assets.setup \
+ && mix assets.deploy \
+ && cd ../../
# Compile the release
RUN mix compile
@@ -70,6 +70,6 @@ WORKDIR /app
ENV MIX_ENV="prod"
# Only copy the final release from the build stage
-COPY --from=builder /app/_build/${MIX_ENV}/rel/firezone ./
+COPY --from=builder /app/_build/${MIX_ENV}/rel/${APP_NAME} ./
CMD ["/app/bin/server"]
diff --git a/apps/api/lib/api/client/views/interface.ex b/apps/api/lib/api/client/views/interface.ex
deleted file mode 100644
index ee5725d95..000000000
--- a/apps/api/lib/api/client/views/interface.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-defmodule API.Client.Views.Interface do
- alias Domain.Clients
-
- def render(%Clients.Client{} = client) do
- %{
- upstream_dns: Domain.Config.fetch_config!(:default_client_dns),
- ipv4: client.ipv4,
- ipv6: client.ipv6
- }
- end
-end
diff --git a/apps/api/lib/api/client/channel.ex b/apps/api/lib/api/device/channel.ex
similarity index 86%
rename from apps/api/lib/api/client/channel.ex
rename to apps/api/lib/api/device/channel.ex
index bca406934..bda876e27 100644
--- a/apps/api/lib/api/client/channel.ex
+++ b/apps/api/lib/api/device/channel.ex
@@ -1,10 +1,10 @@
-defmodule API.Client.Channel do
+defmodule API.Device.Channel do
use API, :channel
- alias API.Client.Views
- alias Domain.{Clients, Resources, Gateways, Relays}
+ alias API.Device.Views
+ alias Domain.{Devices, Resources, Gateways, Relays}
@impl true
- def join("client", _payload, socket) do
+ def join("device", _payload, socket) do
expires_in =
DateTime.diff(socket.assigns.subject.expires_at, DateTime.utc_now(), :millisecond)
@@ -24,10 +24,10 @@ defmodule API.Client.Channel do
:ok =
push(socket, "init", %{
resources: Views.Resource.render_many(resources),
- interface: Views.Interface.render(socket.assigns.client)
+ interface: Views.Interface.render(socket.assigns.device)
})
- :ok = Clients.connect_client(socket.assigns.client)
+ :ok = Devices.connect_device(socket.assigns.device)
{:noreply, socket}
end
@@ -93,8 +93,8 @@ defmodule API.Client.Channel do
"request_connection",
%{
"resource_id" => resource_id,
- "client_rtc_session_description" => client_rtc_session_description,
- "client_preshared_key" => preshared_key
+ "device_rtc_session_description" => device_rtc_session_description,
+ "device_preshared_key" => preshared_key
},
socket
) do
@@ -109,11 +109,11 @@ defmodule API.Client.Channel do
API.Gateway.Socket.id(gateway),
{:request_connection, {self(), socket_ref(socket)},
%{
- client_id: socket.assigns.client.id,
+ device_id: socket.assigns.device.id,
resource_id: resource_id,
authorization_expires_at: socket.assigns.expires_at,
- client_rtc_session_description: client_rtc_session_description,
- client_preshared_key: preshared_key
+ device_rtc_session_description: device_rtc_session_description,
+ device_preshared_key: preshared_key
}}
)
diff --git a/apps/api/lib/api/client/socket.ex b/apps/api/lib/api/device/socket.ex
similarity index 68%
rename from apps/api/lib/api/client/socket.ex
rename to apps/api/lib/api/device/socket.ex
index 28fd718e6..dcfadc75a 100644
--- a/apps/api/lib/api/client/socket.ex
+++ b/apps/api/lib/api/device/socket.ex
@@ -1,10 +1,10 @@
-defmodule API.Client.Socket do
+defmodule API.Device.Socket do
use Phoenix.Socket
- alias Domain.{Auth, Clients}
+ alias Domain.{Auth, Devices}
## Channels
- channel "client:*", API.Client.Channel
+ channel "device:*", API.Device.Channel
## Authentication
@@ -13,11 +13,11 @@ defmodule API.Client.Socket do
%{user_agent: user_agent, peer_data: %{address: remote_ip}} = connect_info
with {:ok, subject} <- Auth.sign_in(token, user_agent, remote_ip),
- {:ok, client} <- Clients.upsert_client(attrs, subject) do
+ {:ok, device} <- Devices.upsert_device(attrs, subject) do
socket =
socket
|> assign(:subject, subject)
- |> assign(:client, client)
+ |> assign(:device, device)
{:ok, socket}
else
@@ -31,5 +31,5 @@ defmodule API.Client.Socket do
end
@impl true
- def id(socket), do: "client:#{socket.assigns.client.id}"
+ def id(socket), do: "device:#{socket.assigns.device.id}"
end
diff --git a/apps/api/lib/api/device/views/interface.ex b/apps/api/lib/api/device/views/interface.ex
new file mode 100644
index 000000000..2b587a771
--- /dev/null
+++ b/apps/api/lib/api/device/views/interface.ex
@@ -0,0 +1,15 @@
+defmodule API.Device.Views.Interface do
+ alias Domain.Devices
+
+ def render(%Devices.Device{} = device) do
+ upstream_dns =
+ Devices.fetch_device_config!(device)
+ |> Keyword.fetch!(:upstream_dns)
+
+ %{
+ upstream_dns: upstream_dns,
+ ipv4: device.ipv4,
+ ipv6: device.ipv6
+ }
+ end
+end
diff --git a/apps/api/lib/api/client/views/relay.ex b/apps/api/lib/api/device/views/relay.ex
similarity index 96%
rename from apps/api/lib/api/client/views/relay.ex
rename to apps/api/lib/api/device/views/relay.ex
index dada01721..59133ff40 100644
--- a/apps/api/lib/api/client/views/relay.ex
+++ b/apps/api/lib/api/device/views/relay.ex
@@ -1,4 +1,4 @@
-defmodule API.Client.Views.Relay do
+defmodule API.Device.Views.Relay do
alias Domain.Relays
def render_many(relays, expires_at) do
diff --git a/apps/api/lib/api/client/views/resource.ex b/apps/api/lib/api/device/views/resource.ex
similarity index 87%
rename from apps/api/lib/api/client/views/resource.ex
rename to apps/api/lib/api/device/views/resource.ex
index 1419f04ad..c6f13ffb9 100644
--- a/apps/api/lib/api/client/views/resource.ex
+++ b/apps/api/lib/api/device/views/resource.ex
@@ -1,4 +1,4 @@
-defmodule API.Client.Views.Resource do
+defmodule API.Device.Views.Resource do
alias Domain.Resources
def render_many(resources) do
diff --git a/apps/api/lib/api/endpoint.ex b/apps/api/lib/api/endpoint.ex
index 4b839645c..1c3db72ff 100644
--- a/apps/api/lib/api/endpoint.ex
+++ b/apps/api/lib/api/endpoint.ex
@@ -17,7 +17,7 @@ defmodule API.Endpoint do
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
socket "/gateway", API.Gateway.Socket, API.Sockets.options()
- socket "/client", API.Client.Socket, API.Sockets.options()
+ socket "/device", API.Device.Socket, API.Sockets.options()
socket "/relay", API.Relay.Socket, API.Sockets.options()
def external_trusted_proxies do
diff --git a/apps/api/lib/api/gateway/channel.ex b/apps/api/lib/api/gateway/channel.ex
index 7f58de876..88c511543 100644
--- a/apps/api/lib/api/gateway/channel.ex
+++ b/apps/api/lib/api/gateway/channel.ex
@@ -1,7 +1,7 @@
defmodule API.Gateway.Channel do
use API, :channel
alias API.Gateway.Views
- alias Domain.{Clients, Resources, Relays, Gateways}
+ alias Domain.{Devices, Resources, Relays, Gateways}
@impl true
def join("gateway", _payload, socket) do
@@ -26,14 +26,14 @@ defmodule API.Gateway.Channel do
def handle_info({:request_connection, {channel_pid, socket_ref}, attrs}, socket) do
%{
- client_id: client_id,
+ device_id: device_id,
resource_id: resource_id,
authorization_expires_at: authorization_expires_at,
- client_rtc_session_description: rtc_session_description,
- client_preshared_key: preshared_key
+ device_rtc_session_description: rtc_session_description,
+ device_preshared_key: preshared_key
} = attrs
- client = Clients.fetch_client_by_id!(client_id, preload: [:actor])
+ device = Devices.fetch_device_by_id!(device_id, preload: [:actor])
resource = Resources.fetch_resource_by_id!(resource_id)
{:ok, relays} = Relays.list_connected_relays_for_resource(resource)
@@ -41,10 +41,10 @@ defmodule API.Gateway.Channel do
push(socket, "request_connection", %{
ref: ref,
- actor: Views.Actor.render(client.actor),
+ actor: Views.Actor.render(device.actor),
relays: Views.Relay.render_many(relays, authorization_expires_at),
resource: Views.Resource.render(resource),
- client: Views.Client.render(client, rtc_session_description, preshared_key),
+ device: Views.Device.render(device, rtc_session_description, preshared_key),
expires_at: DateTime.to_unix(authorization_expires_at, :second)
})
@@ -81,7 +81,7 @@ defmodule API.Gateway.Channel do
# "ended_at" => ended_at,
# "metrics" => [
# %{
- # "client_id" => client_id,
+ # "device_id" => device_id,
# "resource_id" => resource_id,
# "rx_bytes" => 0,
# "tx_packets" => 0
diff --git a/apps/api/lib/api/gateway/socket.ex b/apps/api/lib/api/gateway/socket.ex
index f93dde965..0eb8ee1eb 100644
--- a/apps/api/lib/api/gateway/socket.ex
+++ b/apps/api/lib/api/gateway/socket.ex
@@ -8,32 +8,17 @@ defmodule API.Gateway.Socket do
## Authentication
- def encode_token!(%Gateways.Token{value: value} = token) when not is_nil(value) do
- body = {token.id, token.value}
- config = fetch_config!()
- key_base = Keyword.fetch!(config, :key_base)
- salt = Keyword.fetch!(config, :salt)
- Plug.Crypto.sign(key_base, salt, body)
- end
-
@impl true
def connect(%{"token" => encrypted_secret} = attrs, socket, connect_info) do
%{user_agent: user_agent, peer_data: %{address: remote_ip}} = connect_info
- config = fetch_config!()
- key_base = Keyword.fetch!(config, :key_base)
- salt = Keyword.fetch!(config, :salt)
- max_age = Keyword.fetch!(config, :max_age)
-
attrs =
attrs
|> Map.take(~w[external_id name_suffix public_key])
|> Map.put("last_seen_user_agent", user_agent)
|> Map.put("last_seen_remote_ip", remote_ip)
- with {:ok, {id, secret}} <-
- Plug.Crypto.verify(key_base, salt, encrypted_secret, max_age: max_age),
- {:ok, token} <- Gateways.use_token_by_id_and_secret(id, secret),
+ with {:ok, token} <- Gateways.authorize_gateway(encrypted_secret),
{:ok, gateway} <- Gateways.upsert_gateway(token, attrs) do
socket =
socket
@@ -44,11 +29,7 @@ defmodule API.Gateway.Socket do
end
def connect(_params, _socket, _connect_info) do
- {:error, :invalid}
- end
-
- defp fetch_config! do
- Domain.Config.fetch_env!(:api, __MODULE__)
+ {:error, :missing_token}
end
@impl true
diff --git a/apps/api/lib/api/gateway/views/client.ex b/apps/api/lib/api/gateway/views/client.ex
index 34cf2bd97..cc35fc18c 100644
--- a/apps/api/lib/api/gateway/views/client.ex
+++ b/apps/api/lib/api/gateway/views/client.ex
@@ -1,16 +1,16 @@
-defmodule API.Gateway.Views.Client do
- alias Domain.Clients
+defmodule API.Gateway.Views.Device do
+ alias Domain.Devices
- def render(%Clients.Client{} = client, client_rtc_session_description, preshared_key) do
+ def render(%Devices.Device{} = device, device_rtc_session_description, preshared_key) do
%{
- id: client.id,
- rtc_session_description: client_rtc_session_description,
+ id: device.id,
+ rtc_session_description: device_rtc_session_description,
peer: %{
persistent_keepalive: 25,
- public_key: client.public_key,
+ public_key: device.public_key,
preshared_key: preshared_key,
- ipv4: client.ipv4,
- ipv6: client.ipv6
+ ipv4: device.ipv4,
+ ipv6: device.ipv6
}
}
end
diff --git a/apps/api/lib/api/gateway/views/relay.ex b/apps/api/lib/api/gateway/views/relay.ex
index 49e4ab650..5e7ef948b 100644
--- a/apps/api/lib/api/gateway/views/relay.ex
+++ b/apps/api/lib/api/gateway/views/relay.ex
@@ -1,5 +1,5 @@
defmodule API.Gateway.Views.Relay do
def render_many(relays, expires_at) do
- Enum.flat_map(relays, &API.Client.Views.Relay.render(&1, expires_at))
+ Enum.flat_map(relays, &API.Device.Views.Relay.render(&1, expires_at))
end
end
diff --git a/apps/api/lib/api/relay/socket.ex b/apps/api/lib/api/relay/socket.ex
index 86473bada..0a0098f8b 100644
--- a/apps/api/lib/api/relay/socket.ex
+++ b/apps/api/lib/api/relay/socket.ex
@@ -8,32 +8,17 @@ defmodule API.Relay.Socket do
## Authentication
- def encode_token!(%Relays.Token{value: value} = token) when not is_nil(value) do
- body = {token.id, token.value}
- config = fetch_config!()
- key_base = Keyword.fetch!(config, :key_base)
- salt = Keyword.fetch!(config, :salt)
- Plug.Crypto.sign(key_base, salt, body)
- end
-
@impl true
def connect(%{"token" => encrypted_secret} = attrs, socket, connect_info) do
%{user_agent: user_agent, peer_data: %{address: remote_ip}} = connect_info
- config = fetch_config!()
- key_base = Keyword.fetch!(config, :key_base)
- salt = Keyword.fetch!(config, :salt)
- max_age = Keyword.fetch!(config, :max_age)
-
attrs =
attrs
|> Map.take(~w[ipv4 ipv6])
|> Map.put("last_seen_user_agent", user_agent)
|> Map.put("last_seen_remote_ip", remote_ip)
- with {:ok, {id, secret}} <-
- Plug.Crypto.verify(key_base, salt, encrypted_secret, max_age: max_age),
- {:ok, token} <- Relays.use_token_by_id_and_secret(id, secret),
+ with {:ok, token} <- Relays.authorize_relay(encrypted_secret),
{:ok, relay} <- Relays.upsert_relay(token, attrs) do
socket =
socket
@@ -44,11 +29,7 @@ defmodule API.Relay.Socket do
end
def connect(_params, _socket, _connect_info) do
- {:error, :invalid}
- end
-
- defp fetch_config! do
- Domain.Config.fetch_env!(:api, __MODULE__)
+ {:error, :missing_token}
end
@impl true
diff --git a/apps/api/test/api/client/channel_test.exs b/apps/api/test/api/client/channel_test.exs
index 779478b27..e9930d599 100644
--- a/apps/api/test/api/client/channel_test.exs
+++ b/apps/api/test/api/client/channel_test.exs
@@ -1,14 +1,15 @@
-defmodule API.Client.ChannelTest do
+defmodule API.Device.ChannelTest do
use API.ChannelCase
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures, ResourcesFixtures}
- alias Domain.{ClientsFixtures, RelaysFixtures, GatewaysFixtures}
+ alias Domain.{ConfigFixtures, DevicesFixtures, RelaysFixtures, GatewaysFixtures}
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ ConfigFixtures.upsert_configuration(account: account, devices_upstream_dns: ["1.1.1.1"])
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(actor: actor, account: account)
subject = AuthFixtures.create_subject(identity)
- client = ClientsFixtures.create_client(subject: subject)
+ device = DevicesFixtures.create_device(subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account)
resource =
@@ -20,20 +21,20 @@ defmodule API.Client.ChannelTest do
expires_at = DateTime.utc_now() |> DateTime.add(30, :second)
{:ok, _reply, socket} =
- API.Client.Socket
- |> socket("client:#{client.id}", %{
- client: client,
+ API.Device.Socket
+ |> socket("device:#{device.id}", %{
+ device: device,
subject: subject,
expires_at: expires_at
})
- |> subscribe_and_join(API.Client.Channel, "client")
+ |> subscribe_and_join(API.Device.Channel, "device")
%{
account: account,
actor: actor,
identity: identity,
subject: subject,
- client: client,
+ device: device,
gateway: gateway,
resource: resource,
socket: socket
@@ -41,30 +42,30 @@ defmodule API.Client.ChannelTest do
end
describe "join/3" do
- test "tracks presence after join", %{client: client} do
- presence = Domain.Clients.Presence.list("clients")
+ test "tracks presence after join", %{device: device} do
+ presence = Domain.Devices.Presence.list("devices")
- assert %{metas: [%{online_at: online_at, phx_ref: _ref}]} = Map.fetch!(presence, client.id)
+ assert %{metas: [%{online_at: online_at, phx_ref: _ref}]} = Map.fetch!(presence, device.id)
assert is_number(online_at)
end
- test "expires the channel when token is expired", %{client: client, subject: subject} do
+ test "expires the channel when token is expired", %{device: device, subject: subject} do
expires_at = DateTime.utc_now() |> DateTime.add(25, :millisecond)
subject = %{subject | expires_at: expires_at}
{:ok, _reply, _socket} =
- API.Client.Socket
- |> socket("client:#{client.id}", %{
- client: client,
+ API.Device.Socket
+ |> socket("device:#{device.id}", %{
+ device: device,
subject: subject
})
- |> subscribe_and_join(API.Client.Channel, "client")
+ |> subscribe_and_join(API.Device.Channel, "device")
assert_push "token_expired", %{}, 250
end
test "sends list of resources after join", %{
- client: client,
+ device: device,
resource: resource
} do
assert_push "init", %{resources: resources, interface: interface}
@@ -79,11 +80,10 @@ defmodule API.Client.ChannelTest do
]
assert interface == %{
- ipv4: client.ipv4,
- ipv6: client.ipv6,
+ ipv4: device.ipv4,
+ ipv6: device.ipv6,
upstream_dns: [
- %Postgrex.INET{address: {1, 1, 1, 1}, netmask: nil},
- %Postgrex.INET{address: {1, 0, 0, 1}, netmask: nil}
+ %Postgrex.INET{address: {1, 1, 1, 1}}
]
}
end
@@ -154,8 +154,8 @@ defmodule API.Client.ChannelTest do
test "returns error when resource is not found", %{socket: socket} do
attrs = %{
"resource_id" => Ecto.UUID.generate(),
- "client_rtc_session_description" => "RTC_SD",
- "client_preshared_key" => "PSK"
+ "device_rtc_session_description" => "RTC_SD",
+ "device_preshared_key" => "PSK"
}
ref = push(socket, "request_connection", attrs)
@@ -168,8 +168,8 @@ defmodule API.Client.ChannelTest do
} do
attrs = %{
"resource_id" => resource.id,
- "client_rtc_session_description" => "RTC_SD",
- "client_preshared_key" => "PSK"
+ "device_rtc_session_description" => "RTC_SD",
+ "device_preshared_key" => "PSK"
}
ref = push(socket, "request_connection", attrs)
@@ -183,8 +183,8 @@ defmodule API.Client.ChannelTest do
} do
attrs = %{
"resource_id" => resource.id,
- "client_rtc_session_description" => "RTC_SD",
- "client_preshared_key" => "PSK"
+ "device_rtc_session_description" => "RTC_SD",
+ "device_preshared_key" => "PSK"
}
gateway = GatewaysFixtures.create_gateway(account: account)
@@ -197,20 +197,20 @@ defmodule API.Client.ChannelTest do
test "broadcasts request_connection to the gateways and then returns connect message", %{
resource: resource,
gateway: gateway,
- client: client,
+ device: device,
socket: socket
} do
public_key = gateway.public_key
resource_id = resource.id
- client_id = client.id
+ device_id = device.id
:ok = Domain.Gateways.connect_gateway(gateway)
Phoenix.PubSub.subscribe(Domain.PubSub, API.Gateway.Socket.id(gateway))
attrs = %{
"resource_id" => resource.id,
- "client_rtc_session_description" => "RTC_SD",
- "client_preshared_key" => "PSK"
+ "device_rtc_session_description" => "RTC_SD",
+ "device_preshared_key" => "PSK"
}
ref = push(socket, "request_connection", attrs)
@@ -219,9 +219,9 @@ defmodule API.Client.ChannelTest do
assert %{
resource_id: ^resource_id,
- client_id: ^client_id,
- client_preshared_key: "PSK",
- client_rtc_session_description: "RTC_SD",
+ device_id: ^device_id,
+ device_preshared_key: "PSK",
+ device_rtc_session_description: "RTC_SD",
authorization_expires_at: authorization_expires_at
} = payload
diff --git a/apps/api/test/api/client/socket_test.exs b/apps/api/test/api/client/socket_test.exs
index 986ce56ba..45d6bcc45 100644
--- a/apps/api/test/api/client/socket_test.exs
+++ b/apps/api/test/api/client/socket_test.exs
@@ -1,9 +1,9 @@
-defmodule API.Client.SocketTest do
+defmodule API.Device.SocketTest do
use API.ChannelCase, async: true
- import API.Client.Socket, only: [id: 1]
- alias API.Client.Socket
+ import API.Device.Socket, only: [id: 1]
+ alias API.Device.Socket
alias Domain.Auth
- alias Domain.{AuthFixtures, ClientsFixtures}
+ alias Domain.{AuthFixtures, DevicesFixtures}
@connect_info %{
user_agent: "iOS/12.7 (iPhone) connlib/0.1.1",
@@ -20,41 +20,41 @@ defmodule API.Client.SocketTest do
assert connect(Socket, attrs, @connect_info) == {:error, :invalid}
end
- test "creates a new client" do
+ test "creates a new device" do
subject = AuthFixtures.create_subject()
{:ok, token} = Auth.create_session_token_from_subject(subject)
attrs = connect_attrs(token: token)
assert {:ok, socket} = connect(Socket, attrs, connect_info(subject))
- assert client = Map.fetch!(socket.assigns, :client)
+ assert device = Map.fetch!(socket.assigns, :device)
- assert client.external_id == attrs["external_id"]
- assert client.public_key == attrs["public_key"]
- assert client.last_seen_user_agent == subject.context.user_agent
- assert client.last_seen_remote_ip.address == subject.context.remote_ip
- assert client.last_seen_version == "0.7.412"
+ assert device.external_id == attrs["external_id"]
+ assert device.public_key == attrs["public_key"]
+ assert device.last_seen_user_agent == subject.context.user_agent
+ assert device.last_seen_remote_ip.address == subject.context.remote_ip
+ assert device.last_seen_version == "0.7.412"
end
- test "updates existing client" do
+ test "updates existing device" do
subject = AuthFixtures.create_subject()
- existing_client = ClientsFixtures.create_client(subject: subject)
+ existing_device = DevicesFixtures.create_device(subject: subject)
{:ok, token} = Auth.create_session_token_from_subject(subject)
- attrs = connect_attrs(token: token, external_id: existing_client.external_id)
+ attrs = connect_attrs(token: token, external_id: existing_device.external_id)
assert {:ok, socket} = connect(Socket, attrs, connect_info(subject))
- assert client = Repo.one(Domain.Clients.Client)
- assert client.id == socket.assigns.client.id
+ assert device = Repo.one(Domain.Devices.Device)
+ assert device.id == socket.assigns.device.id
end
end
describe "id/1" do
- test "creates a channel for a client" do
- client = ClientsFixtures.create_client()
- socket = socket(API.Client.Socket, "", %{client: client})
+ test "creates a channel for a device" do
+ device = DevicesFixtures.create_device()
+ socket = socket(API.Device.Socket, "", %{device: device})
- assert id(socket) == "client:#{client.id}"
+ assert id(socket) == "device:#{device.id}"
end
end
@@ -66,7 +66,7 @@ defmodule API.Client.SocketTest do
end
defp connect_attrs(attrs) do
- ClientsFixtures.client_attrs()
+ DevicesFixtures.device_attrs()
|> Map.take(~w[external_id public_key]a)
|> Map.merge(Enum.into(attrs, %{}))
|> Enum.into(%{}, fn {k, v} -> {to_string(k), v} end)
diff --git a/apps/api/test/api/gateway/channel_test.exs b/apps/api/test/api/gateway/channel_test.exs
index b0f334d51..0d2bc4e53 100644
--- a/apps/api/test/api/gateway/channel_test.exs
+++ b/apps/api/test/api/gateway/channel_test.exs
@@ -1,14 +1,14 @@
defmodule API.Gateway.ChannelTest do
use API.ChannelCase
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures, ResourcesFixtures}
- alias Domain.{ClientsFixtures, RelaysFixtures, GatewaysFixtures}
+ alias Domain.{DevicesFixtures, RelaysFixtures, GatewaysFixtures}
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(actor: actor, account: account)
subject = AuthFixtures.create_subject(identity)
- client = ClientsFixtures.create_client(subject: subject)
+ device = DevicesFixtures.create_device(subject: subject)
gateway = GatewaysFixtures.create_gateway(account: account)
resource =
@@ -29,7 +29,7 @@ defmodule API.Gateway.ChannelTest do
actor: actor,
identity: identity,
subject: subject,
- client: client,
+ device: device,
gateway: gateway,
resource: resource,
relay: relay,
@@ -63,7 +63,7 @@ defmodule API.Gateway.ChannelTest do
describe "handle_info/2 :request_connection" do
test "pushes request_connection message", %{
- client: client,
+ device: device,
resource: resource,
relay: relay,
socket: socket
@@ -81,18 +81,18 @@ defmodule API.Gateway.ChannelTest do
socket.channel_pid,
{:request_connection, {channel_pid, socket_ref},
%{
- client_id: client.id,
+ device_id: device.id,
resource_id: resource.id,
authorization_expires_at: expires_at,
- client_rtc_session_description: rtc_session_description,
- client_preshared_key: preshared_key
+ device_rtc_session_description: rtc_session_description,
+ device_preshared_key: preshared_key
}}
)
assert_push "request_connection", payload
assert is_binary(payload.ref)
- assert payload.actor == %{id: client.actor_id}
+ assert payload.actor == %{id: device.actor_id}
ipv4_stun_uri = "stun:#{relay.ipv4}:#{relay.port}"
ipv4_turn_uri = "turn:#{relay.ipv4}:#{relay.port}"
@@ -138,14 +138,14 @@ defmodule API.Gateway.ChannelTest do
ipv6: resource.ipv6
}
- assert payload.client == %{
- id: client.id,
+ assert payload.device == %{
+ id: device.id,
peer: %{
- ipv4: client.ipv4,
- ipv6: client.ipv6,
+ ipv4: device.ipv4,
+ ipv6: device.ipv6,
persistent_keepalive: 25,
preshared_key: preshared_key,
- public_key: client.public_key
+ public_key: device.public_key
},
rtc_session_description: rtc_session_description
}
@@ -155,8 +155,8 @@ defmodule API.Gateway.ChannelTest do
end
describe "handle_in/3 connection_ready" do
- test "forwards RFC session description to the client channel", %{
- client: client,
+ test "forwards RFC session description to the device channel", %{
+ device: device,
resource: resource,
relay: relay,
gateway: gateway,
@@ -176,11 +176,11 @@ defmodule API.Gateway.ChannelTest do
socket.channel_pid,
{:request_connection, {channel_pid, socket_ref},
%{
- client_id: client.id,
+ device_id: device.id,
resource_id: resource.id,
authorization_expires_at: expires_at,
- client_rtc_session_description: rtc_session_description,
- client_preshared_key: preshared_key
+ device_rtc_session_description: rtc_session_description,
+ device_preshared_key: preshared_key
}}
)
diff --git a/apps/api/test/api/gateway/socket_test.exs b/apps/api/test/api/gateway/socket_test.exs
index 61083fefb..4a37a790f 100644
--- a/apps/api/test/api/gateway/socket_test.exs
+++ b/apps/api/test/api/gateway/socket_test.exs
@@ -2,6 +2,7 @@ defmodule API.Gateway.SocketTest do
use API.ChannelCase, async: true
import API.Gateway.Socket, except: [connect: 3]
alias API.Gateway.Socket
+ alias Domain.Gateways
alias Domain.GatewaysFixtures
@connlib_version "0.1.1"
@@ -11,28 +12,14 @@ defmodule API.Gateway.SocketTest do
peer_data: %{address: {189, 172, 73, 153}}
}
- describe "encode_token!/1" do
- test "returns encoded token" do
- token = GatewaysFixtures.create_token()
- assert encrypted_secret = encode_token!(token)
-
- config = Application.fetch_env!(:api, Socket)
- key_base = Keyword.fetch!(config, :key_base)
- salt = Keyword.fetch!(config, :salt)
-
- assert Plug.Crypto.verify(key_base, salt, encrypted_secret) ==
- {:ok, {token.id, token.value}}
- end
- end
-
describe "connect/3" do
test "returns error when token is missing" do
- assert connect(Socket, %{}, @connect_info) == {:error, :invalid}
+ assert connect(Socket, %{}, @connect_info) == {:error, :missing_token}
end
test "creates a new gateway" do
token = GatewaysFixtures.create_token()
- encrypted_secret = encode_token!(token)
+ encrypted_secret = Gateways.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret)
@@ -49,7 +36,7 @@ defmodule API.Gateway.SocketTest do
test "updates existing gateway" do
token = GatewaysFixtures.create_token()
existing_gateway = GatewaysFixtures.create_gateway(token: token)
- encrypted_secret = encode_token!(token)
+ encrypted_secret = Gateways.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret, external_id: existing_gateway.external_id)
@@ -60,7 +47,7 @@ defmodule API.Gateway.SocketTest do
test "returns error when token is invalid" do
attrs = connect_attrs(token: "foo")
- assert connect(Socket, attrs, @connect_info) == {:error, :invalid}
+ assert connect(Socket, attrs, @connect_info) == {:error, :invalid_token}
end
end
diff --git a/apps/api/test/api/relay/socket_test.exs b/apps/api/test/api/relay/socket_test.exs
index 29dfd434e..1043b001f 100644
--- a/apps/api/test/api/relay/socket_test.exs
+++ b/apps/api/test/api/relay/socket_test.exs
@@ -2,6 +2,7 @@ defmodule API.Relay.SocketTest do
use API.ChannelCase, async: true
import API.Relay.Socket, except: [connect: 3]
alias API.Relay.Socket
+ alias Domain.Relays
alias Domain.RelaysFixtures
@connlib_version "0.1.1"
@@ -11,28 +12,14 @@ defmodule API.Relay.SocketTest do
peer_data: %{address: {189, 172, 73, 153}}
}
- describe "encode_token!/1" do
- test "returns encoded token" do
- token = RelaysFixtures.create_token()
- assert encrypted_secret = encode_token!(token)
-
- config = Application.fetch_env!(:api, Socket)
- key_base = Keyword.fetch!(config, :key_base)
- salt = Keyword.fetch!(config, :salt)
-
- assert Plug.Crypto.verify(key_base, salt, encrypted_secret) ==
- {:ok, {token.id, token.value}}
- end
- end
-
describe "connect/3" do
test "returns error when token is missing" do
- assert connect(Socket, %{}, @connect_info) == {:error, :invalid}
+ assert connect(Socket, %{}, @connect_info) == {:error, :missing_token}
end
test "creates a new relay" do
token = RelaysFixtures.create_token()
- encrypted_secret = encode_token!(token)
+ encrypted_secret = Relays.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret)
@@ -49,7 +36,7 @@ defmodule API.Relay.SocketTest do
test "updates existing relay" do
token = RelaysFixtures.create_token()
existing_relay = RelaysFixtures.create_relay(token: token)
- encrypted_secret = encode_token!(token)
+ encrypted_secret = Relays.encode_token!(token)
attrs = connect_attrs(token: encrypted_secret, ipv4: existing_relay.ipv4)
@@ -60,7 +47,7 @@ defmodule API.Relay.SocketTest do
test "returns error when token is invalid" do
attrs = connect_attrs(token: "foo")
- assert connect(Socket, attrs, @connect_info) == {:error, :invalid}
+ assert connect(Socket, attrs, @connect_info) == {:error, :invalid_token}
end
end
diff --git a/apps/api/test/support/channel_case.ex b/apps/api/test/support/channel_case.ex
index 40f760830..8b558cf2a 100644
--- a/apps/api/test/support/channel_case.ex
+++ b/apps/api/test/support/channel_case.ex
@@ -3,7 +3,7 @@ defmodule API.ChannelCase do
use Domain.CaseTemplate
@presences [
- Domain.Clients.Presence,
+ Domain.Devices.Presence,
Domain.Gateways.Presence,
Domain.Relays.Presence
]
diff --git a/apps/domain/lib/domain/actors.ex b/apps/domain/lib/domain/actors.ex
index 9db327dbb..7482470ac 100644
--- a/apps/domain/lib/domain/actors.ex
+++ b/apps/domain/lib/domain/actors.ex
@@ -1,11 +1,11 @@
defmodule Domain.Actors do
- alias Domain.{Repo, Auth, Validator, Telemetry}
+ alias Domain.{Repo, Auth, Validator}
alias Domain.Actors.{Authorizer, Actor}
require Ecto.Query
- def fetch_count_by_role(role, %Auth.Subject{} = subject) do
+ def fetch_count_by_type(type, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
- Actor.Query.by_role(role)
+ Actor.Query.by_type(type)
|> Authorizer.for_subject(subject)
|> Repo.aggregate(:count)
end
@@ -61,7 +61,7 @@ defmodule Domain.Actors do
:ok <- Auth.ensure_has_access_to(subject, provider),
changeset = Actor.Changeset.create_changeset(provider, attrs),
{:ok, data} <- Ecto.Changeset.apply_action(changeset, :validate) do
- granted_permissions = Auth.fetch_role_permissions!(data.role)
+ granted_permissions = Auth.fetch_type_permissions!(data.type)
if MapSet.subset?(granted_permissions, subject.permissions) do
create_actor(provider, provider_identifier, attrs)
@@ -84,7 +84,6 @@ defmodule Domain.Actors do
|> Repo.transaction()
|> case do
{:ok, %{actor: actor, identity: identity}} ->
- Telemetry.add_actor()
{:ok, %{actor | identities: [identity]}}
{:error, _step, changeset, _effects_so_far} ->
@@ -92,26 +91,26 @@ defmodule Domain.Actors do
end
end
- def change_actor_role(%Actor{} = actor, role, %Auth.Subject{} = subject) do
+ def change_actor_type(%Actor{} = actor, type, %Auth.Subject{} = subject) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do
Actor.Query.by_id(actor.id)
|> Authorizer.for_subject(subject)
|> Repo.fetch_and_update(
with: fn actor ->
- changeset = Actor.Changeset.set_actor_role(actor, role)
+ changeset = Actor.Changeset.set_actor_type(actor, type)
cond do
- changeset.data.role != :admin ->
+ changeset.data.type != :admin ->
changeset
- changeset.changes.role == :admin ->
+ changeset.changes.type == :admin ->
changeset
other_enabled_admins_exist?(actor) ->
changeset
true ->
- :cant_remove_admin_role
+ :cant_remove_admin_type
end
end
)
@@ -158,8 +157,12 @@ defmodule Domain.Actors do
end
end
- defp other_enabled_admins_exist?(%Actor{role: :admin, account_id: account_id, id: id}) do
- Actor.Query.by_role(:admin)
+ defp other_enabled_admins_exist?(%Actor{
+ type: :account_admin_user,
+ account_id: account_id,
+ id: id
+ }) do
+ Actor.Query.by_type(:account_admin_user)
|> Actor.Query.not_disabled()
|> Actor.Query.by_account_id(account_id)
|> Actor.Query.by_id({:not, id})
diff --git a/apps/domain/lib/domain/actors/actor.ex b/apps/domain/lib/domain/actors/actor.ex
index 3c53c9951..4e009b4b3 100644
--- a/apps/domain/lib/domain/actors/actor.ex
+++ b/apps/domain/lib/domain/actors/actor.ex
@@ -2,8 +2,7 @@ defmodule Domain.Actors.Actor do
use Domain, :schema
schema "actors" do
- field :type, Ecto.Enum, values: [:user, :service_account]
- field :role, Ecto.Enum, values: [:unprivileged, :admin]
+ field :type, Ecto.Enum, values: [:account_user, :account_admin_user, :service_account]
# TODO:
# field :first_name, :string
@@ -14,9 +13,6 @@ defmodule Domain.Actors.Actor do
# belongs_to :group, Domain.Actors.Group
belongs_to :account, Domain.Accounts.Account
- # has_many :oidc_connections, Domain.Auth.OIDC.Connection
- has_many :api_tokens, Domain.ApiTokens.ApiToken
-
field :disabled_at, :utc_datetime_usec
field :deleted_at, :utc_datetime_usec
timestamps()
diff --git a/apps/domain/lib/domain/actors/actor/changeset.ex b/apps/domain/lib/domain/actors/actor/changeset.ex
index 3aef88d95..3a1d8b3ea 100644
--- a/apps/domain/lib/domain/actors/actor/changeset.ex
+++ b/apps/domain/lib/domain/actors/actor/changeset.ex
@@ -5,15 +5,15 @@ defmodule Domain.Actors.Actor.Changeset do
def create_changeset(%Auth.Provider{} = provider, attrs) do
%Actors.Actor{}
- |> cast(attrs, ~w[type role]a)
- |> validate_required(~w[type role]a)
+ |> cast(attrs, ~w[type]a)
+ |> validate_required(~w[type]a)
|> put_change(:account_id, provider.account_id)
end
- def set_actor_role(actor, role) do
+ def set_actor_type(actor, type) when type in [:account_user, :account_admin_user] do
actor
|> change()
- |> put_change(:role, role)
+ |> put_change(:type, type)
end
def disable_actor(actor) do
diff --git a/apps/domain/lib/domain/actors/actor/query.ex b/apps/domain/lib/domain/actors/actor/query.ex
index f6aba8ace..0e75e34e5 100644
--- a/apps/domain/lib/domain/actors/actor/query.ex
+++ b/apps/domain/lib/domain/actors/actor/query.ex
@@ -20,8 +20,8 @@ defmodule Domain.Actors.Actor.Query do
where(queryable, [actors: actors], actors.account_id == ^account_id)
end
- def by_role(queryable \\ all(), role) do
- where(queryable, [actors: actors], actors.role == ^role)
+ def by_type(queryable \\ all(), type) do
+ where(queryable, [actors: actors], actors.type == ^type)
end
def not_disabled(queryable \\ all()) do
diff --git a/apps/domain/lib/domain/actors/authorizer.ex b/apps/domain/lib/domain/actors/authorizer.ex
index c4a77864f..9e64434a9 100644
--- a/apps/domain/lib/domain/actors/authorizer.ex
+++ b/apps/domain/lib/domain/actors/authorizer.ex
@@ -6,14 +6,14 @@ defmodule Domain.Actors.Authorizer do
def edit_own_profile_permission, do: build(Actor, :edit_own_profile)
@impl Domain.Auth.Authorizer
- def list_permissions_for_role(:admin) do
+ def list_permissions_for_role(:account_admin_user) do
[
manage_actors_permission(),
edit_own_profile_permission()
]
end
- def list_permissions_for_role(:unprivileged) do
+ def list_permissions_for_role(:account_user) do
[
edit_own_profile_permission()
]
diff --git a/apps/domain/lib/domain/api_tokens.ex b/apps/domain/lib/domain/api_tokens.ex
deleted file mode 100644
index 20f6e0e79..000000000
--- a/apps/domain/lib/domain/api_tokens.ex
+++ /dev/null
@@ -1,133 +0,0 @@
-defmodule Domain.ApiTokens do
- alias Domain.{Repo, Validator, Auth}
- alias Domain.Actors
- alias Domain.ApiTokens.Authorizer
- alias Domain.ApiTokens.ApiToken
-
- def count_by_actor_id(actor_id) do
- ApiToken.Query.by_actor_id(actor_id)
- |> Repo.aggregate(:count)
- end
-
- def list_api_tokens(%Auth.Subject{} = subject) do
- required_permissions =
- {:one_of,
- [
- Authorizer.manage_api_tokens_permission(),
- Authorizer.manage_own_api_tokens_permission()
- ]}
-
- with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
- ApiToken.Query.all()
- |> Authorizer.for_subject(subject)
- |> Repo.list()
- end
- end
-
- def list_api_tokens_by_actor_id(actor_id, %Auth.Subject{} = subject) do
- required_permissions =
- {:one_of,
- [
- Authorizer.manage_api_tokens_permission(),
- Authorizer.manage_own_api_tokens_permission()
- ]}
-
- with true <- Validator.valid_uuid?(actor_id),
- :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
- ApiToken.Query.by_actor_id(actor_id)
- |> Authorizer.for_subject(subject)
- |> Repo.list()
- else
- false -> {:ok, []}
- {:error, reason} -> {:error, reason}
- end
- end
-
- def fetch_api_token_by_id(id, %Auth.Subject{} = subject) do
- required_permissions =
- {:one_of,
- [
- Authorizer.manage_api_tokens_permission(),
- Authorizer.manage_own_api_tokens_permission()
- ]}
-
- with true <- Validator.valid_uuid?(id),
- :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
- ApiToken.Query.by_id(id)
- |> Authorizer.for_subject(subject)
- |> Repo.fetch()
- else
- false -> {:error, :not_found}
- {:error, reason} -> {:error, reason}
- end
- end
-
- def fetch_unexpired_api_token_by_id(id, %Auth.Subject{} = subject) do
- required_permissions =
- {:one_of,
- [
- Authorizer.manage_api_tokens_permission(),
- Authorizer.manage_own_api_tokens_permission()
- ]}
-
- with true <- Validator.valid_uuid?(id),
- :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
- ApiToken.Query.by_id(id)
- |> Authorizer.for_subject(subject)
- |> ApiToken.Query.not_expired()
- |> Repo.fetch()
- else
- false -> {:error, :not_found}
- {:error, reason} -> {:error, reason}
- end
- end
-
- def fetch_unexpired_api_token_by_id(id) do
- if Validator.valid_uuid?(id) do
- ApiToken.Query.by_id(id)
- |> ApiToken.Query.not_expired()
- |> Repo.fetch()
- else
- {:error, :not_found}
- end
- end
-
- def new_api_token(attrs \\ %{}) do
- ApiToken.Changeset.changeset(attrs)
- end
-
- def create_api_token(attrs, %Auth.Subject{} = subject) do
- with :ok <-
- Auth.ensure_has_permissions(
- subject,
- Authorizer.manage_own_api_tokens_permission()
- ) do
- create_api_token(subject.actor, attrs)
- end
- end
-
- def create_api_token(%Actors.Actor{} = actor, attrs) do
- count_by_actor_id = count_by_actor_id(actor.id)
- changeset = ApiToken.Changeset.create_changeset(actor, attrs, max: count_by_actor_id)
-
- with {:ok, api_token} <- Repo.insert(changeset) do
- Domain.Telemetry.create_api_token()
- {:ok, api_token}
- end
- end
-
- def api_token_expired?(%ApiToken{} = api_token) do
- DateTime.diff(api_token.expires_at, DateTime.utc_now()) < 0
- end
-
- def delete_api_token_by_id(api_token_id, %Auth.Subject{} = subject) do
- with {:ok, api_token} <- fetch_api_token_by_id(api_token_id, subject),
- :ok <- Authorizer.ensure_can_manage(subject, api_token) do
- {:ok, Repo.delete!(api_token)}
- else
- {:error, :not_found} -> {:error, :not_found}
- {:error, {:unauthorized, context}} -> {:error, {:unauthorized, context}}
- {:error, :unauthorized} -> {:error, :not_found}
- end
- end
-end
diff --git a/apps/domain/lib/domain/api_tokens/api_token.ex b/apps/domain/lib/domain/api_tokens/api_token.ex
deleted file mode 100644
index 794d6a2c9..000000000
--- a/apps/domain/lib/domain/api_tokens/api_token.ex
+++ /dev/null
@@ -1,14 +0,0 @@
-defmodule Domain.ApiTokens.ApiToken do
- use Domain, :schema
-
- schema "api_tokens" do
- field :expires_at, :utc_datetime_usec
-
- # Developer-friendly way to set expires_at
- field :expires_in, :integer, virtual: true, default: 30
-
- belongs_to :actor, Domain.Actors.Actor
-
- timestamps(updated_at: false)
- end
-end
diff --git a/apps/domain/lib/domain/api_tokens/api_token/changeset.ex b/apps/domain/lib/domain/api_tokens/api_token/changeset.ex
deleted file mode 100644
index e13152b9c..000000000
--- a/apps/domain/lib/domain/api_tokens/api_token/changeset.ex
+++ /dev/null
@@ -1,44 +0,0 @@
-defmodule Domain.ApiTokens.ApiToken.Changeset do
- use Domain, :changeset
- alias Domain.ApiTokens.ApiToken
-
- @max_per_user 25
-
- def create_changeset(user, attrs, opts \\ []) do
- changeset(attrs)
- |> put_change(:user_id, user.id)
- |> assoc_constraint(:user)
- |> maybe_validate_count_per_user(@max_per_user, opts[:max])
- end
-
- def changeset(api_token \\ %ApiToken{}, attrs) do
- api_token
- |> cast(attrs, ~w[
- expires_in
- expires_at
- ]a)
- |> validate_required([:expires_in])
- |> validate_number(:expires_in, greater_than_or_equal_to: 1, less_than_or_equal_to: 90)
- |> resolve_expires_at()
- |> validate_required(:expires_at)
- end
-
- def max_per_user, do: @max_per_user
-
- defp resolve_expires_at(changeset) do
- expires_at =
- DateTime.utc_now()
- |> DateTime.add(get_field(changeset, :expires_in), :day)
-
- put_change(changeset, :expires_at, expires_at)
- end
-
- defp maybe_validate_count_per_user(changeset, max, num) when is_integer(num) and num >= max do
- # XXX: This suffers from a race condition because the count happens in a separate transaction.
- # At the moment it's not a big concern. Fixing it would require locking against INSERTs or DELETEs
- # while counts are happening.
- add_error(changeset, :base, "token limit of #{@max_per_user} reached")
- end
-
- defp maybe_validate_count_per_user(changeset, _, _), do: changeset
-end
diff --git a/apps/domain/lib/domain/api_tokens/api_token/query.ex b/apps/domain/lib/domain/api_tokens/api_token/query.ex
deleted file mode 100644
index c7eaa854f..000000000
--- a/apps/domain/lib/domain/api_tokens/api_token/query.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-defmodule Domain.ApiTokens.ApiToken.Query do
- use Domain, :query
-
- def all do
- from(api_tokens in Domain.ApiTokens.ApiToken, as: :api_tokens)
- end
-
- def by_id(queryable \\ all(), id) do
- where(queryable, [api_tokens: api_tokens], api_tokens.id == ^id)
- end
-
- def by_actor_id(queryable \\ all(), actor_id) do
- where(queryable, [api_tokens: api_tokens], api_tokens.actor_id == ^actor_id)
- end
-
- def not_expired(queryable \\ all()) do
- where(queryable, [api_tokens: api_tokens], api_tokens.expires_at >= fragment("NOW()"))
- end
-end
diff --git a/apps/domain/lib/domain/api_tokens/authorizer.ex b/apps/domain/lib/domain/api_tokens/authorizer.ex
deleted file mode 100644
index 9ffe489cf..000000000
--- a/apps/domain/lib/domain/api_tokens/authorizer.ex
+++ /dev/null
@@ -1,50 +0,0 @@
-defmodule Domain.ApiTokens.Authorizer do
- use Domain.Auth.Authorizer
- alias Domain.ApiTokens.ApiToken
-
- def manage_own_api_tokens_permission, do: build(ApiToken, :manage_own)
- def manage_api_tokens_permission, do: build(ApiToken, :manage)
-
- @impl Domain.Auth.Authorizer
- def list_permissions_for_role(:admin) do
- [
- manage_own_api_tokens_permission(),
- manage_api_tokens_permission()
- ]
- end
-
- def list_permissions_for_role(_role) do
- []
- end
-
- @impl Domain.Auth.Authorizer
- def for_subject(queryable, %Subject{} = subject) do
- cond do
- has_permission?(subject, manage_api_tokens_permission()) ->
- queryable
-
- has_permission?(subject, manage_own_api_tokens_permission()) ->
- %{actor_id: actor_id} = subject.identity
- ApiToken.Query.by_actor_id(queryable, actor_id)
- end
- end
-
- def ensure_can_manage(%Subject{} = subject, %ApiToken{} = api_token) do
- cond do
- has_permission?(subject, manage_api_tokens_permission()) ->
- :ok
-
- has_permission?(subject, manage_own_api_tokens_permission()) ->
- %{type: :user, id: actor_id} = subject.identity
-
- if api_token.actor_id == actor_id do
- :ok
- else
- {:error, :unauthorized}
- end
-
- true ->
- {:error, :unauthorized}
- end
- end
-end
diff --git a/apps/domain/lib/domain/application.ex b/apps/domain/lib/domain/application.ex
index 6b178ffcc..0c258804c 100644
--- a/apps/domain/lib/domain/application.ex
+++ b/apps/domain/lib/domain/application.ex
@@ -2,14 +2,9 @@ defmodule Domain.Application do
use Application
def start(_type, _args) do
- result =
- Supervisor.start_link(children(), strategy: :one_for_one, name: __MODULE__.Supervisor)
-
- :ok = after_start()
- result
+ Supervisor.start_link(children(), strategy: :one_for_one, name: __MODULE__.Supervisor)
end
- # TODO: when app starts for migrations set env to disable connectivity checks and telemetry
def children do
[
# Infrastructure services
@@ -20,20 +15,10 @@ defmodule Domain.Application do
Domain.Auth,
Domain.Relays,
Domain.Gateways,
- Domain.Clients,
+ Domain.Devices
# Observability
- Domain.Telemetry
+ # Domain.Telemetry
]
end
-
- if Mix.env() == :prod do
- defp after_start do
- Domain.Config.validate_runtime_config!()
- end
- else
- defp after_start do
- :ok
- end
- end
end
diff --git a/apps/domain/lib/domain/auth.ex b/apps/domain/lib/domain/auth.ex
index 2e0cdb727..c15980aed 100644
--- a/apps/domain/lib/domain/auth.ex
+++ b/apps/domain/lib/domain/auth.ex
@@ -7,8 +7,8 @@ defmodule Domain.Auth do
alias Domain.Auth.{Adapters, Provider}
@default_session_duration_hours %{
- admin: 3,
- unprivileged: 24 * 7
+ account_admin_user: 3,
+ account_user: 24 * 7
}
def start_link(opts) do
@@ -25,6 +25,11 @@ defmodule Domain.Auth do
# Providers
+ def fetch_provider_by_id(id) do
+ Provider.Query.by_id(id)
+ |> Repo.fetch()
+ end
+
def create_provider(%Accounts.Account{} = account, attrs, %Subject{} = subject) do
with :ok <- ensure_has_permissions(subject, Authorizer.manage_providers_permission()),
:ok <- Accounts.ensure_has_access_to(subject, account) do
@@ -182,9 +187,8 @@ defmodule Domain.Auth do
end
def sign_in(session_token, user_agent, remote_ip) do
- with {:ok, identity_id} <- verify_session_token(session_token, user_agent, remote_ip),
- {:ok, expires_at} <- fetch_session_token_expires_at(session_token),
- {:ok, identity} <- fetch_identity_by_id(identity_id) do
+ with {:ok, identity, expires_at} <-
+ verify_session_token(session_token, user_agent, remote_ip) do
{:ok, build_subject(identity, expires_at, user_agent, remote_ip)}
else
{:error, :not_found} -> {:error, :unauthorized}
@@ -209,7 +213,7 @@ defmodule Domain.Auth do
|> Repo.update!()
identity_with_preloads = Repo.preload(identity, [:account, :actor])
- permissions = fetch_role_permissions!(identity_with_preloads.actor.role)
+ permissions = fetch_type_permissions!(identity_with_preloads.actor.type)
%Subject{
identity: identity,
@@ -222,7 +226,7 @@ defmodule Domain.Auth do
end
defp build_subject_expires_at(%Actors.Actor{} = actor, expires_at) do
- default_session_duration_hours = Map.fetch!(@default_session_duration_hours, actor.role)
+ default_session_duration_hours = Map.fetch!(@default_session_duration_hours, actor.type)
expires_at || DateTime.utc_now() |> DateTime.add(default_session_duration_hours, :hour)
end
@@ -238,6 +242,16 @@ defmodule Domain.Auth do
{:ok, Plug.Crypto.sign(key_base, salt, payload, max_age: max_age)}
end
+ def create_access_token_for_identity(%Identity{} = identity) do
+ config = fetch_config!()
+ key_base = Keyword.fetch!(config, :key_base)
+ salt = Keyword.fetch!(config, :salt)
+ payload = {:identity, identity.id, identity.provider_virtual_state.secret, :ignore}
+ {:ok, expires_at, 0} = DateTime.from_iso8601(identity.provider_state["expires_at"])
+ max_age = DateTime.diff(expires_at, DateTime.utc_now(), :second)
+ {:ok, Plug.Crypto.sign(key_base, salt, payload, max_age: max_age)}
+ end
+
def fetch_session_token_expires_at(token, opts \\ []) do
config = fetch_config!()
key_base = Keyword.fetch!(config, :key_base)
@@ -258,9 +272,6 @@ defmodule Domain.Auth do
end
end
- defp session_token_payload(%Subject{identity: %Identity{} = identity, context: context}),
- do: {:identity, identity.id, session_context_payload(context.remote_ip, context.user_agent)}
-
defp session_context_payload(remote_ip, user_agent)
when is_tuple(remote_ip) and is_binary(user_agent) do
:crypto.hash(:sha256, :erlang.term_to_binary({remote_ip, user_agent}))
@@ -271,23 +282,51 @@ defmodule Domain.Auth do
key_base = Keyword.fetch!(config, :key_base)
salt = Keyword.fetch!(config, :salt)
- context_payload = session_context_payload(remote_ip, user_agent)
-
case Plug.Crypto.verify(key_base, salt, token) do
- {:ok, {:identity, identity_id, ^context_payload}} ->
- {:ok, identity_id}
-
- {:ok, {_type, _id, _context_payload}} ->
- {:error, :unauthorized_browser}
-
- {:error, :invalid} ->
- {:error, :invalid_token}
-
- {:error, :expired} ->
- {:error, :expired_token}
+ {:ok, payload} -> verify_session_token_payload(token, payload, user_agent, remote_ip)
+ {:error, :invalid} -> {:error, :invalid_token}
+ {:error, :expired} -> {:error, :expired_token}
end
end
+ defp verify_session_token_payload(
+ _token,
+ {:identity, identity_id, secret, :ignore},
+ _user_agent,
+ _remote_ip
+ ) do
+ with {:ok, identity} <- fetch_identity_by_id(identity_id),
+ {:ok, provider} <- fetch_provider_by_id(identity.provider_id),
+ {:ok, identity, expires_at} <-
+ Adapters.verify_secret(provider, identity, secret) do
+ {:ok, identity, expires_at}
+ else
+ {:error, :invalid_secret} -> {:error, :invalid_token}
+ {:error, :expired_secret} -> {:error, :expired_token}
+ {:error, :not_found} -> {:error, :not_found}
+ end
+ end
+
+ defp verify_session_token_payload(
+ token,
+ {:identity, identity_id, context_payload},
+ user_agent,
+ remote_ip
+ ) do
+ with {:ok, identity} <- fetch_identity_by_id(identity_id),
+ true <- context_payload == session_context_payload(remote_ip, user_agent),
+ {:ok, expires_at} <- fetch_session_token_expires_at(token) do
+ {:ok, identity, expires_at}
+ else
+ false -> {:error, :unauthorized_browser}
+ other -> other
+ end
+ end
+
+ defp session_token_payload(%Subject{identity: %Identity{} = identity, context: context}) do
+ {:identity, identity.id, session_context_payload(context.remote_ip, context.user_agent)}
+ end
+
defp fetch_config! do
Config.fetch_env!(:domain, __MODULE__)
end
@@ -311,11 +350,11 @@ defmodule Domain.Auth do
ensure_has_permissions(subject, required_permissions) == :ok
end
- def fetch_role_permissions!(%Role{} = role),
- do: role.permissions
+ def fetch_type_permissions!(%Role{} = type),
+ do: type.permissions
- def fetch_role_permissions!(role_name) when is_atom(role_name),
- do: role_name |> Roles.build() |> fetch_role_permissions!()
+ def fetch_type_permissions!(type_name) when is_atom(type_name),
+ do: type_name |> Roles.build() |> fetch_type_permissions!()
# Authorization
diff --git a/apps/domain/lib/domain/auth/adapters.ex b/apps/domain/lib/domain/auth/adapters.ex
index c78dceddc..2f9c16cce 100644
--- a/apps/domain/lib/domain/auth/adapters.ex
+++ b/apps/domain/lib/domain/auth/adapters.ex
@@ -5,7 +5,8 @@ defmodule Domain.Auth.Adapters do
@adapters %{
email: Domain.Auth.Adapters.Email,
openid_connect: Domain.Auth.Adapters.OpenIDConnect,
- userpass: Domain.Auth.Adapters.UserPass
+ userpass: Domain.Auth.Adapters.UserPass,
+ token: Domain.Auth.Adapters.Token
}
@adapter_names Map.keys(@adapters)
diff --git a/apps/domain/lib/domain/auth/adapters/email.ex b/apps/domain/lib/domain/auth/adapters/email.ex
index bd80a285a..4026a2ef0 100644
--- a/apps/domain/lib/domain/auth/adapters/email.ex
+++ b/apps/domain/lib/domain/auth/adapters/email.ex
@@ -52,8 +52,8 @@ defmodule Domain.Auth.Adapters.Email do
{
%{
- sign_in_token_hash: Domain.Crypto.hash(sign_in_token),
- sign_in_token_created_at: DateTime.utc_now()
+ "sign_in_token_hash" => Domain.Crypto.hash(sign_in_token),
+ "sign_in_token_created_at" => DateTime.utc_now()
},
%{
sign_in_token: sign_in_token
@@ -109,6 +109,11 @@ defmodule Domain.Auth.Adapters.Email do
end
end
+ defp sign_in_token_expired?(%DateTime{} = sign_in_token_created_at) do
+ now = DateTime.utc_now()
+ DateTime.diff(now, sign_in_token_created_at, :second) > @sign_in_token_expiration_seconds
+ end
+
defp sign_in_token_expired?(sign_in_token_created_at) do
now = DateTime.utc_now()
diff --git a/apps/domain/lib/domain/auth/adapters/token.ex b/apps/domain/lib/domain/auth/adapters/token.ex
new file mode 100644
index 000000000..59b5070f8
--- /dev/null
+++ b/apps/domain/lib/domain/auth/adapters/token.ex
@@ -0,0 +1,123 @@
+defmodule Domain.Auth.Adapters.Token do
+ @moduledoc """
+ This is not recommended to use in production,
+ it's only for development, testing, and small home labs.
+ """
+ use Supervisor
+ alias Domain.Repo
+ alias Domain.Auth.{Identity, Provider, Adapter}
+ alias Domain.Auth.Adapters.Token.State
+
+ @behaviour Adapter
+
+ def start_link(_init_arg) do
+ Supervisor.start_link(__MODULE__, nil, name: __MODULE__)
+ end
+
+ @impl true
+ def init(_init_arg) do
+ children = []
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+
+ @impl true
+ def identity_changeset(%Provider{} = _provider, %Ecto.Changeset{} = changeset) do
+ changeset
+ |> Domain.Validator.trim_change(:provider_identifier)
+ |> put_hash_and_expiration()
+ end
+
+ defp put_hash_and_expiration(changeset) do
+ secret = Domain.Crypto.rand_token(32)
+ secret_hash = Domain.Crypto.hash(secret)
+
+ data = Map.get(changeset.data, :provider_virtual_state) || %{}
+ attrs = Ecto.Changeset.get_change(changeset, :provider_virtual_state) || %{}
+
+ Ecto.embedded_load(State, data, :json)
+ |> State.Changeset.changeset(attrs)
+ |> Ecto.Changeset.put_change(:secret_hash, secret_hash)
+ |> case do
+ %{valid?: false} = nested_changeset ->
+ {changeset, _original_type} =
+ Domain.Changeset.inject_embedded_changeset(
+ changeset,
+ :provider_virtual_state,
+ nested_changeset
+ )
+
+ changeset
+
+ %{valid?: true} = nested_changeset ->
+ expires_at = Ecto.Changeset.fetch_change!(nested_changeset, :expires_at)
+
+ changeset
+ |> Ecto.Changeset.put_change(:provider_state, %{
+ "expires_at" => DateTime.to_iso8601(expires_at),
+ "secret_hash" => secret_hash
+ })
+ |> Ecto.Changeset.put_change(:provider_virtual_state, %{
+ secret: secret
+ })
+ end
+ end
+
+ @impl true
+ def ensure_provisioned(%Ecto.Changeset{} = changeset) do
+ changeset
+ end
+
+ @impl true
+ def ensure_deprovisioned(%Ecto.Changeset{} = changeset) do
+ changeset
+ end
+
+ @impl true
+ def verify_secret(%Identity{} = identity, secret) when is_binary(secret) do
+ Identity.Query.by_id(identity.id)
+ |> Repo.fetch_and_update(
+ with: fn identity ->
+ secret_hash = identity.provider_state["secret_hash"]
+ secret_expires_at = identity.provider_state["expires_at"]
+
+ cond do
+ is_nil(secret_hash) ->
+ :invalid_secret
+
+ is_nil(secret_expires_at) ->
+ :invalid_secret
+
+ sign_in_token_expired?(secret_expires_at) ->
+ :expired_secret
+
+ not Domain.Crypto.equal?(secret, secret_hash) ->
+ :invalid_secret
+
+ true ->
+ Ecto.Changeset.change(identity)
+ end
+ end
+ )
+ |> case do
+ {:ok, identity} ->
+ {:ok, expires_at, 0} = DateTime.from_iso8601(identity.provider_state["expires_at"])
+ {:ok, identity, expires_at}
+
+ {:error, reason} ->
+ {:error, reason}
+ end
+ end
+
+ defp sign_in_token_expired?(secret_expires_at) do
+ now = DateTime.utc_now()
+
+ case DateTime.from_iso8601(secret_expires_at) do
+ {:ok, secret_expires_at, 0} ->
+ DateTime.diff(secret_expires_at, now, :second) < 0
+
+ {:error, _reason} ->
+ true
+ end
+ end
+end
diff --git a/apps/domain/lib/domain/auth/adapters/token/state.ex b/apps/domain/lib/domain/auth/adapters/token/state.ex
new file mode 100644
index 000000000..c0e1e3aec
--- /dev/null
+++ b/apps/domain/lib/domain/auth/adapters/token/state.ex
@@ -0,0 +1,9 @@
+defmodule Domain.Auth.Adapters.Token.State do
+ use Domain, :schema
+
+ @primary_key false
+ embedded_schema do
+ field :secret_hash, :string, redact: true
+ field :expires_at, :utc_datetime_usec
+ end
+end
diff --git a/apps/domain/lib/domain/auth/adapters/token/state/changeset.ex b/apps/domain/lib/domain/auth/adapters/token/state/changeset.ex
new file mode 100644
index 000000000..a8f659563
--- /dev/null
+++ b/apps/domain/lib/domain/auth/adapters/token/state/changeset.ex
@@ -0,0 +1,17 @@
+defmodule Domain.Auth.Adapters.Token.State.Changeset do
+ use Domain, :changeset
+ alias Domain.Auth.Adapters.Token.State
+
+ @fields ~w[expires_at]a
+
+ def create_changeset(attrs) do
+ changeset(%State{}, attrs)
+ end
+
+ def changeset(struct, attrs) do
+ struct
+ |> cast(attrs, @fields)
+ |> validate_required(@fields)
+ |> validate_datetime(:expires_at, greater_than: DateTime.utc_now())
+ end
+end
diff --git a/apps/domain/lib/domain/auth/adapters/userpass.ex b/apps/domain/lib/domain/auth/adapters/userpass.ex
index 480754082..87d01004c 100644
--- a/apps/domain/lib/domain/auth/adapters/userpass.ex
+++ b/apps/domain/lib/domain/auth/adapters/userpass.ex
@@ -49,7 +49,7 @@ defmodule Domain.Auth.Adapters.UserPass do
password_hash = Ecto.Changeset.fetch_change!(nested_changeset, :password_hash)
changeset
- |> Ecto.Changeset.put_change(:provider_state, %{password_hash: password_hash})
+ |> Ecto.Changeset.put_change(:provider_state, %{"password_hash" => password_hash})
|> Ecto.Changeset.put_change(:provider_virtual_state, %{})
end
end
diff --git a/apps/domain/lib/domain/auth/authorizer.ex b/apps/domain/lib/domain/auth/authorizer.ex
index 9da0281ce..99a03fd0a 100644
--- a/apps/domain/lib/domain/auth/authorizer.ex
+++ b/apps/domain/lib/domain/auth/authorizer.ex
@@ -37,14 +37,14 @@ defmodule Domain.Auth.Authorizer do
def manage_identities_permission, do: build(Auth.Identity, :manage)
def manage_own_identities_permission, do: build(Auth.Identity, :manage_own)
- def list_permissions_for_role(:admin) do
+ def list_permissions_for_role(:account_admin_user) do
[
manage_providers_permission(),
manage_identities_permission()
]
end
- def list_permissions_for_role(:unprivileged) do
+ def list_permissions_for_role(:account_user) do
[
manage_own_identities_permission()
]
@@ -60,11 +60,9 @@ defmodule Domain.Auth.Authorizer do
Auth.Identity.Query.by_account_id(queryable, subject.account.id)
Auth.has_permission?(subject, manage_own_identities_permission()) ->
- %{type: :user, id: actor_id} = subject.actor
-
queryable
|> Auth.Identity.Query.by_account_id(subject.account.id)
- |> Auth.Identity.Query.by_actor_id(actor_id)
+ |> Auth.Identity.Query.by_actor_id(subject.actor.id)
end
end
diff --git a/apps/domain/lib/domain/auth/provider.ex b/apps/domain/lib/domain/auth/provider.ex
index a322bd4d1..cae9774de 100644
--- a/apps/domain/lib/domain/auth/provider.ex
+++ b/apps/domain/lib/domain/auth/provider.ex
@@ -4,7 +4,7 @@ defmodule Domain.Auth.Provider do
schema "auth_providers" do
field :name, :string
- field :adapter, Ecto.Enum, values: ~w[email openid_connect userpass]a
+ field :adapter, Ecto.Enum, values: ~w[email openid_connect userpass token]a
field :adapter_config, :map
belongs_to :account, Domain.Accounts.Account
diff --git a/apps/domain/lib/domain/auth/roles.ex b/apps/domain/lib/domain/auth/roles.ex
index 6b51c7be4..3968b0fd0 100644
--- a/apps/domain/lib/domain/auth/roles.ex
+++ b/apps/domain/lib/domain/auth/roles.ex
@@ -3,8 +3,8 @@ defmodule Domain.Auth.Roles do
def list_roles do
[
- build(:admin),
- build(:unprivileged)
+ build(:account_admin_user),
+ build(:account_user)
]
end
@@ -12,8 +12,7 @@ defmodule Domain.Auth.Roles do
[
Domain.Auth.Authorizer,
Domain.Config.Authorizer,
- Domain.ApiTokens.Authorizer,
- Domain.Clients.Authorizer,
+ Domain.Devices.Authorizer,
Domain.Gateways.Authorizer,
Domain.Relays.Authorizer,
Domain.Actors.Authorizer,
diff --git a/apps/domain/lib/domain/changeset.ex b/apps/domain/lib/domain/changeset.ex
index f9d1bad3d..89184891b 100644
--- a/apps/domain/lib/domain/changeset.ex
+++ b/apps/domain/lib/domain/changeset.ex
@@ -91,9 +91,17 @@ defmodule Domain.Changeset do
|> get_change(field)
|> apply_action!(:dump)
|> Ecto.embedded_dump(:json)
+ |> atom_keys_to_string()
changeset = %{changeset | types: Map.put(changeset.types, field, original_type)}
put_change(changeset, field, map)
end
+
+ # We dump atoms to strings because if we persist to Postgres and read it,
+ # the map will be returned with string keys, and we want to make sure that
+ # the map handling is unified across the codebase.
+ defp atom_keys_to_string(map) do
+ for {k, v} <- map, into: %{}, do: {to_string(k), v}
+ end
end
diff --git a/apps/domain/lib/domain/clients.ex b/apps/domain/lib/domain/clients.ex
deleted file mode 100644
index a9d9b0e53..000000000
--- a/apps/domain/lib/domain/clients.ex
+++ /dev/null
@@ -1,170 +0,0 @@
-defmodule Domain.Clients do
- use Supervisor
- alias Domain.{Repo, Auth, Validator}
- alias Domain.Actors
- alias Domain.Clients.{Client, Authorizer, Presence}
-
- def start_link(opts) do
- Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
- end
-
- def init(_opts) do
- children = [
- Presence
- ]
-
- Supervisor.init(children, strategy: :one_for_one)
- end
-
- def count_by_account_id(account_id) do
- Client.Query.by_account_id(account_id)
- |> Repo.aggregate(:count)
- end
-
- def count_by_actor_id(actor_id) do
- Client.Query.by_actor_id(actor_id)
- |> Repo.aggregate(:count)
- end
-
- def fetch_client_by_id(id, %Auth.Subject{} = subject) do
- required_permissions =
- {:one_of,
- [
- Authorizer.manage_clients_permission(),
- Authorizer.manage_own_clients_permission()
- ]}
-
- with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
- true <- Validator.valid_uuid?(id) do
- Client.Query.by_id(id)
- |> Authorizer.for_subject(subject)
- |> Repo.fetch()
- else
- false -> {:error, :not_found}
- other -> other
- end
- end
-
- def fetch_client_by_id!(id, opts \\ []) do
- {preload, _opts} = Keyword.pop(opts, :preload, [])
-
- Client.Query.by_id(id)
- |> Repo.one!()
- |> Repo.preload(preload)
- end
-
- def list_clients(%Auth.Subject{} = subject) do
- required_permissions =
- {:one_of,
- [
- Authorizer.manage_clients_permission(),
- Authorizer.manage_own_clients_permission()
- ]}
-
- with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
- Client.Query.all()
- |> Authorizer.for_subject(subject)
- |> Repo.list()
- end
- end
-
- def list_clients_for_actor(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
- list_clients_by_actor_id(actor.id, subject)
- end
-
- def list_clients_by_actor_id(actor_id, %Auth.Subject{} = subject) do
- required_permissions =
- {:one_of,
- [
- Authorizer.manage_clients_permission(),
- Authorizer.manage_own_clients_permission()
- ]}
-
- with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
- true <- Validator.valid_uuid?(actor_id) do
- Client.Query.by_actor_id(actor_id)
- |> Authorizer.for_subject(subject)
- |> Repo.list()
- else
- false -> {:error, :not_found}
- other -> other
- end
- end
-
- def change_client(%Client{} = client, attrs \\ %{}) do
- Client.Changeset.update_changeset(client, attrs)
- end
-
- def upsert_client(attrs \\ %{}, %Auth.Subject{identity: %Auth.Identity{} = identity} = subject) do
- with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_own_clients_permission()) do
- changeset = Client.Changeset.upsert_changeset(identity, subject.context, attrs)
-
- Ecto.Multi.new()
- |> Ecto.Multi.insert(:client, changeset,
- conflict_target: Client.Changeset.upsert_conflict_target(),
- on_conflict: Client.Changeset.upsert_on_conflict(),
- returning: true
- )
- |> resolve_address_multi(:ipv4)
- |> resolve_address_multi(:ipv6)
- |> Ecto.Multi.update(:client_with_address, fn
- %{client: %Client{} = client, ipv4: ipv4, ipv6: ipv6} ->
- Client.Changeset.finalize_upsert_changeset(client, ipv4, ipv6)
- end)
- |> Repo.transaction()
- |> case do
- {:ok, %{client_with_address: client}} -> {:ok, client}
- {:error, :client, changeset, _effects_so_far} -> {:error, changeset}
- end
- end
- end
-
- defp resolve_address_multi(multi, type) do
- Ecto.Multi.run(multi, type, fn _repo, %{client: %Client{} = client} ->
- if address = Map.get(client, type) do
- {:ok, address}
- else
- {:ok, Domain.Network.fetch_next_available_address!(client.account_id, type)}
- end
- end)
- end
-
- def update_client(%Client{} = client, attrs, %Auth.Subject{} = subject) do
- with :ok <- authorize_actor_client_management(client.actor_id, subject) do
- Client.Query.by_id(client.id)
- |> Authorizer.for_subject(subject)
- |> Repo.fetch_and_update(with: &Client.Changeset.update_changeset(&1, attrs))
- end
- end
-
- def delete_client(%Client{} = client, %Auth.Subject{} = subject) do
- with :ok <- authorize_actor_client_management(client.actor_id, subject) do
- Client.Query.by_id(client.id)
- |> Authorizer.for_subject(subject)
- |> Repo.fetch_and_update(with: &Client.Changeset.delete_changeset/1)
- end
- end
-
- def authorize_actor_client_management(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
- authorize_actor_client_management(actor.id, subject)
- end
-
- def authorize_actor_client_management(actor_id, %Auth.Subject{actor: %{id: actor_id}} = subject) do
- Auth.ensure_has_permissions(subject, Authorizer.manage_own_clients_permission())
- end
-
- def authorize_actor_client_management(_actor_id, %Auth.Subject{} = subject) do
- Auth.ensure_has_permissions(subject, Authorizer.manage_clients_permission())
- end
-
- def connect_client(%Client{} = client) do
- Phoenix.PubSub.subscribe(Domain.PubSub, "actor:#{client.actor_id}")
-
- {:ok, _} =
- Presence.track(self(), "clients", client.id, %{
- online_at: System.system_time(:second)
- })
-
- :ok
- end
-end
diff --git a/apps/domain/lib/domain/clients/authorizer.ex b/apps/domain/lib/domain/clients/authorizer.ex
deleted file mode 100644
index 705244d88..000000000
--- a/apps/domain/lib/domain/clients/authorizer.ex
+++ /dev/null
@@ -1,41 +0,0 @@
-defmodule Domain.Clients.Authorizer do
- use Domain.Auth.Authorizer
- alias Domain.Clients.Client
-
- def manage_own_clients_permission, do: build(Client, :manage_own)
- def manage_clients_permission, do: build(Client, :manage)
-
- @impl Domain.Auth.Authorizer
-
- def list_permissions_for_role(:admin) do
- [
- manage_own_clients_permission(),
- manage_clients_permission()
- ]
- end
-
- def list_permissions_for_role(:unprivileged) do
- [
- manage_own_clients_permission()
- ]
- end
-
- def list_permissions_for_role(_) do
- []
- end
-
- @impl Domain.Auth.Authorizer
- def for_subject(queryable, %Subject{} = subject) do
- cond do
- has_permission?(subject, manage_clients_permission()) ->
- Client.Query.by_account_id(queryable, subject.account.id)
-
- has_permission?(subject, manage_own_clients_permission()) ->
- %{type: :user, id: actor_id} = subject.actor
-
- queryable
- |> Client.Query.by_account_id(subject.account.id)
- |> Client.Query.by_actor_id(actor_id)
- end
- end
-end
diff --git a/apps/domain/lib/domain/clients/client/query.ex b/apps/domain/lib/domain/clients/client/query.ex
deleted file mode 100644
index fdeef4eb8..000000000
--- a/apps/domain/lib/domain/clients/client/query.ex
+++ /dev/null
@@ -1,40 +0,0 @@
-defmodule Domain.Clients.Client.Query do
- use Domain, :query
-
- def all do
- from(clients in Domain.Clients.Client, as: :clients)
- |> where([clients: clients], is_nil(clients.deleted_at))
- end
-
- def by_id(queryable \\ all(), id) do
- where(queryable, [clients: clients], clients.id == ^id)
- end
-
- def by_actor_id(queryable \\ all(), actor_id) do
- where(queryable, [clients: clients], clients.actor_id == ^actor_id)
- end
-
- def by_account_id(queryable \\ all(), account_id) do
- where(queryable, [clients: clients], clients.account_id == ^account_id)
- end
-
- def returning_all(queryable \\ all()) do
- select(queryable, [clients: clients], clients)
- end
-
- def with_preloaded_actor(queryable \\ all()) do
- with_named_binding(queryable, :actor, fn queryable, binding ->
- queryable
- |> join(:inner, [clients: clients], actor in assoc(clients, ^binding), as: ^binding)
- |> preload([clients: clients, actor: actor], actor: actor)
- end)
- end
-
- def with_preloaded_identity(queryable \\ all()) do
- with_named_binding(queryable, :identity, fn queryable, binding ->
- queryable
- |> join(:inner, [clients: clients], identity in assoc(clients, ^binding), as: ^binding)
- |> preload([clients: clients, identity: identity], identity: identity)
- end)
- end
-end
diff --git a/apps/domain/lib/domain/config.ex b/apps/domain/lib/domain/config.ex
index d3523b329..6f732b763 100644
--- a/apps/domain/lib/domain/config.ex
+++ b/apps/domain/lib/domain/config.ex
@@ -4,22 +4,16 @@ defmodule Domain.Config do
alias Domain.Config.{Definition, Definitions, Validator, Errors, Fetcher}
alias Domain.Config.Configuration
- def fetch_source_and_config!(key) do
- db_config = maybe_fetch_db_config!(key)
- env_config = System.get_env()
-
- case Fetcher.fetch_source_and_config(Definitions, key, db_config, env_config) do
- {:ok, source, config} ->
- {source, config}
-
- {:error, reason} ->
- Errors.raise_error!(reason)
+ def fetch_resolved_configs!(account_id, keys, opts \\ []) do
+ for {key, {_source, value}} <-
+ fetch_resolved_configs_with_sources!(account_id, keys, opts),
+ into: %{} do
+ {key, value}
end
end
- def fetch_source_and_configs!(keys) when is_list(keys) do
- db_config = maybe_fetch_db_config!(keys)
- env_config = System.get_env()
+ def fetch_resolved_configs_with_sources!(account_id, keys, opts \\ []) do
+ {db_config, env_config} = maybe_load_sources(account_id, opts, keys)
for key <- keys, into: %{} do
case Fetcher.fetch_source_and_config(Definitions, key, db_config, env_config) do
@@ -32,46 +26,24 @@ defmodule Domain.Config do
end
end
- def fetch_config(key) do
- db_config = maybe_fetch_db_config!(key)
- env_config = System.get_env()
+ defp maybe_load_sources(account_id, opts, keys) when is_list(keys) do
+ ignored_sources = Keyword.get(opts, :ignore_sources, []) |> List.wrap()
- with {:ok, _source, config} <-
- Fetcher.fetch_source_and_config(Definitions, key, db_config, env_config) do
- {:ok, config}
- end
- end
+ one_of_keys_is_stored_in_db? =
+ Enum.any?(keys, &(&1 in Domain.Config.Configuration.__schema__(:fields)))
- def fetch_config!(key) do
- case fetch_config(key) do
- {:ok, config} ->
- config
+ db_config =
+ if :db not in ignored_sources and one_of_keys_is_stored_in_db?,
+ do: get_account_config_by_account_id(account_id),
+ else: %{}
- {:error, reason} ->
- Errors.raise_error!(reason)
- end
- end
+ # credo:disable-for-lines:4
+ env_config =
+ if :env not in ignored_sources,
+ do: System.get_env(),
+ else: %{}
- def fetch_configs!(keys) do
- for {key, {_source, value}} <- fetch_source_and_configs!(keys), into: %{} do
- {key, value}
- end
- end
-
- defp maybe_fetch_db_config!(keys) when is_list(keys) do
- if Enum.any?(keys, &(&1 in Domain.Config.Configuration.__schema__(:fields))) do
- fetch_db_config!()
- else
- %{}
- end
- end
-
- defp maybe_fetch_db_config!(key) do
- if key in Domain.Config.Configuration.__schema__(:fields) do
- fetch_db_config!()
- else
- %{}
- end
+ {db_config, env_config}
end
@doc """
@@ -92,59 +64,32 @@ defmodule Domain.Config do
end
end
- def validate_runtime_config!(
- module \\ Definitions,
- db_config \\ fetch_db_config!(),
- env_config \\ System.get_env()
- ) do
- module.configs()
- |> Enum.flat_map(fn {module, key} ->
- case Fetcher.fetch_source_and_config(module, key, db_config, env_config) do
- {:ok, _source, _value} -> []
- {:error, reason} -> [reason]
- end
- end)
- |> case do
- [] -> :ok
- errors -> Errors.raise_error!(errors)
+ ## Configuration stored in database
+
+ def get_account_config_by_account_id(account_id) do
+ queryable = Configuration.Query.by_account_id(account_id)
+ Repo.one(queryable) || %Configuration{account_id: account_id}
+ end
+
+ def fetch_account_config(%Auth.Subject{} = subject) do
+ with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_permission()) do
+ {:ok, get_account_config_by_account_id(subject.account.id)}
end
end
- def fetch_db_config! do
- Repo.one!(Configuration)
+ def change_account_config(%Configuration{} = configuration, attrs \\ %{}) do
+ Configuration.Changeset.changeset(configuration, attrs)
end
- def fetch_db_config(%Auth.Subject{} = subject) do
- with :ok <- Auth.ensure_has_permissions(subject, Authorizer.configure_permission()) do
- {:ok, fetch_db_config!()}
+ def update_config(%Configuration{} = configuration, attrs, %Auth.Subject{} = subject) do
+ with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_permission()) do
+ update_config(configuration, attrs)
end
end
- def change_config(%Configuration{} = config \\ fetch_db_config!(), attrs \\ %{}) do
- Configuration.Changeset.changeset(config, attrs)
- end
-
- def update_config(%Configuration{} = config, attrs, %Auth.Subject{} = subject) do
- with :ok <- Auth.ensure_has_permissions(subject, Authorizer.configure_permission()) do
- update_config(config, attrs)
- end
- end
-
- def update_config(%Configuration{} = config, attrs) do
- changeset = Configuration.Changeset.changeset(config, attrs)
-
- with {:ok, config} <- Repo.update(changeset) do
- # Domain.Auth.SAML.StartProxy.refresh(config.saml_identity_providers)
- {:ok, config}
- end
- end
-
- def put_config!(key, value) do
- with {:ok, config} <- update_config(fetch_db_config!(), %{key => value}) do
- config
- else
- {:error, reason} -> raise "cannot update config: #{inspect(reason)}"
- end
+ def update_config(%Configuration{} = configuration, attrs) do
+ Configuration.Changeset.changeset(configuration, attrs)
+ |> Repo.insert_or_update()
end
def config_changeset(changeset, schema_key, config_key \\ nil) do
@@ -168,10 +113,7 @@ defmodule Domain.Config do
end
end
- def vpn_sessions_expire? do
- freq = fetch_config!(:vpn_session_duration)
- 0 < freq && freq < Configuration.Changeset.max_vpn_session_duration()
- end
+ ## Test helpers
if Mix.env() != :test do
defdelegate fetch_env!(app, key), to: Application
diff --git a/apps/domain/lib/domain/config/authorizer.ex b/apps/domain/lib/domain/config/authorizer.ex
index da862329c..088a429a9 100644
--- a/apps/domain/lib/domain/config/authorizer.ex
+++ b/apps/domain/lib/domain/config/authorizer.ex
@@ -2,12 +2,12 @@ defmodule Domain.Config.Authorizer do
use Domain.Auth.Authorizer
alias Domain.Config.Configuration
- def configure_permission, do: build(Configuration, :manage)
+ def manage_permission, do: build(Configuration, :manage)
@impl Domain.Auth.Authorizer
- def list_permissions_for_role(:admin) do
+ def list_permissions_for_role(:account_admin_user) do
[
- configure_permission()
+ manage_permission()
]
end
diff --git a/apps/domain/lib/domain/config/configuration.ex b/apps/domain/lib/domain/config/configuration.ex
index bd38b3d36..837415e29 100644
--- a/apps/domain/lib/domain/config/configuration.ex
+++ b/apps/domain/lib/domain/config/configuration.ex
@@ -3,37 +3,12 @@ defmodule Domain.Config.Configuration do
alias Domain.Config.Logo
schema "configurations" do
- # field :upstream_dns, {:array, :string}, default: []
-
- field :allow_unprivileged_device_management, :boolean
- field :allow_unprivileged_device_configuration, :boolean
-
- field :local_auth_enabled, :boolean
- field :disable_vpn_on_oidc_error, :boolean
-
- # The defaults for these fields are set in the following migration:
- # apps/domain/priv/repo/migrations/20221224210654_fix_sites_nullable_fields.exs
- #
- # This will be changing in 0.8 and again when we have client apps,
- # so this works for the time being. The important thing is allowing users
- # to update these fields via the REST API since they were removed as
- # environment variables in the above migration. This is important for users
- # wishing to configure Firezone with automated Infrastructure tools like
- # Terraform.
- field :default_client_persistent_keepalive, :integer
- field :default_client_mtu, :integer
- field :default_client_endpoint, :string
- field :default_client_dns, {:array, :string}, default: []
- field :default_client_allowed_ips, {:array, Domain.Types.INET}, default: []
-
- # XXX: Remove when this feature is refactored into config expiration feature
- # and WireGuard keys are decoupled from devices to facilitate rotation.
- #
- # See https://github.com/firezone/firezone/issues/1236
- field :vpn_session_duration, :integer, read_after_writes: true
+ field :devices_upstream_dns, {:array, :string}, default: []
embeds_one :logo, Logo, on_replace: :delete
+ belongs_to :account, Domain.Accounts.Account
+
timestamps()
end
end
diff --git a/apps/domain/lib/domain/config/configuration/changeset.ex b/apps/domain/lib/domain/config/configuration/changeset.ex
index a7889018c..570a646b3 100644
--- a/apps/domain/lib/domain/config/configuration/changeset.ex
+++ b/apps/domain/lib/domain/config/configuration/changeset.ex
@@ -2,46 +2,28 @@ defmodule Domain.Config.Configuration.Changeset do
use Domain, :changeset
import Domain.Config, only: [config_changeset: 2]
- # Postgres max int size is 4 bytes
- @max_vpn_session_duration 2_147_483_647
+ @fields ~w[devices_upstream_dns]a
- @fields ~w[
- local_auth_enabled
- allow_unprivileged_device_management
- allow_unprivileged_device_configuration
- default_client_persistent_keepalive
- default_client_mtu
- default_client_endpoint
- default_client_dns
- default_client_allowed_ips
- vpn_session_duration
- ]a
-
- @spec changeset(
- {map, map}
- | %{
- :__struct__ => atom | %{:__changeset__ => map, optional(any) => any},
- optional(atom) => any
- },
- :invalid | %{optional(:__struct__) => none, optional(atom | binary) => any}
- ) :: any
def changeset(configuration, attrs) do
changeset =
configuration
|> cast(attrs, @fields)
|> cast_embed(:logo)
- |> trim_change(:default_client_dns)
- |> trim_change(:default_client_endpoint)
+ |> trim_change(:devices_upstream_dns)
Enum.reduce(@fields, changeset, fn field, changeset ->
config_changeset(changeset, field)
end)
- |> ensure_no_overridden_changes()
+ |> ensure_no_overridden_changes(configuration.account_id)
end
- defp ensure_no_overridden_changes(changeset) do
+ defp ensure_no_overridden_changes(changeset, account_id) do
changed_keys = Map.keys(changeset.changes)
- configs = Domain.Config.fetch_source_and_configs!(changed_keys)
+
+ configs =
+ Domain.Config.fetch_resolved_configs_with_sources!(account_id, changed_keys,
+ ignore_sources: :db
+ )
Enum.reduce(changed_keys, changeset, fn key, changeset ->
case Map.fetch!(configs, key) do
@@ -58,6 +40,4 @@ defmodule Domain.Config.Configuration.Changeset do
end
end)
end
-
- def max_vpn_session_duration, do: @max_vpn_session_duration
end
diff --git a/apps/domain/lib/domain/config/configuration/query.ex b/apps/domain/lib/domain/config/configuration/query.ex
new file mode 100644
index 000000000..502f3bcd8
--- /dev/null
+++ b/apps/domain/lib/domain/config/configuration/query.ex
@@ -0,0 +1,15 @@
+defmodule Domain.Config.Configuration.Query do
+ use Domain, :query
+
+ def all do
+ from(configurations in Domain.Config.Configuration, as: :configurations)
+ end
+
+ def by_id(queryable \\ all(), id) do
+ where(queryable, [configurations: configurations], configurations.id == ^id)
+ end
+
+ def by_account_id(queryable \\ all(), account_id) do
+ where(queryable, [configurations: configurations], configurations.account_id == ^account_id)
+ end
+end
diff --git a/apps/domain/lib/domain/config/definitions.ex b/apps/domain/lib/domain/config/definitions.ex
index e011df669..afbf0d925 100644
--- a/apps/domain/lib/domain/config/definitions.ex
+++ b/apps/domain/lib/domain/config/definitions.ex
@@ -1,4 +1,3 @@
-# TODO: clean up unused definitions
defmodule Domain.Config.Definitions do
@moduledoc """
Most day-to-day config of Firezone can be done via the Firezone Web UI,
@@ -40,7 +39,8 @@ defmodule Domain.Config.Definitions do
:external_url,
:phoenix_secure_cookies,
:phoenix_listen_address,
- :phoenix_http_port,
+ :phoenix_http_web_port,
+ :phoenix_http_api_port,
:phoenix_http_protocol_options,
:phoenix_external_trusted_proxies,
:phoenix_private_clients
@@ -57,17 +57,6 @@ defmodule Domain.Config.Definitions do
:database_ssl_opts,
:database_parameters
]},
- {"Admin Setup",
- """
- Options responsible for initial admin provisioning and resetting the admin password.
-
- For more details see [troubleshooting guide](/docs/administer/troubleshoot/#admin-login-isnt-working).
- """,
- [
- :reset_admin_on_boot,
- :default_admin_email,
- :default_admin_password
- ]},
{"Secrets and Encryption",
"""
Your secrets should be generated during installation automatically and persisted to `.env` file.
@@ -75,8 +64,12 @@ defmodule Domain.Config.Definitions do
All secrets should be a **base64-encoded string**.
""",
[
- :guardian_secret_key,
- :database_encryption_key,
+ :auth_token_key_base,
+ :auth_token_salt,
+ :relays_auth_token_key_base,
+ :relays_auth_token_salt,
+ :gateways_auth_token_key_base,
+ :gateways_auth_token_salt,
:secret_key_base,
:live_view_signing_salt,
:cookie_signing_salt,
@@ -84,38 +77,25 @@ defmodule Domain.Config.Definitions do
]},
{"Devices",
[
- :allow_unprivileged_device_management,
- :allow_unprivileged_device_configuration,
- :vpn_session_duration,
- :default_client_persistent_keepalive,
- :default_client_mtu,
- :default_client_endpoint,
- :default_client_dns,
- :default_client_allowed_ips
+ :devices_upstream_dns
]},
- # {"Limits",
- # [
- # :max_devices_per_user
- # ]},
{"Authorization",
+ """
+ Providers:
+
+ * `openid_connect` is used to authenticate users via OpenID Connect, this is recommended for production use;
+ * `email` is used to authenticate users via magic links sent to the email;
+ * `token` is used to authenticate service accounts using an API token;
+ * `userpass` is used to authenticate users with username and password, should be used
+ with extreme care and is not recommended for production use.
+ """,
[
- :local_auth_enabled
+ :auth_provider_adapters
]},
- {"WireGuard",
+ {"Gateways",
[
- :wireguard_port,
- :wireguard_ipv4_enabled,
- :wireguard_ipv4_masquerade,
- :wireguard_ipv4_network,
- :wireguard_ipv4_address,
- :wireguard_ipv6_enabled,
- :wireguard_ipv6_masquerade,
- :wireguard_ipv6_network,
- :wireguard_ipv6_address,
- :wireguard_private_key_path,
- :wireguard_interface_name,
- :gateway_egress_interface,
- :gateway_nft_path
+ :gateway_ipv4_masquerade,
+ :gateway_ipv6_masquerade
]},
{"Outbound Emails",
[
@@ -123,11 +103,6 @@ defmodule Domain.Config.Definitions do
:outbound_email_adapter,
:outbound_email_adapter_opts
]},
- {"Connectivity Checks",
- [
- :connectivity_checks_enabled,
- :connectivity_checks_interval
- ]},
{"Telemetry",
[
:telemetry_enabled,
@@ -169,7 +144,20 @@ defmodule Domain.Config.Definitions do
@doc """
Internal port to listen on for the Phoenix web server.
"""
- defconfig(:phoenix_http_port, :integer,
+ defconfig(:phoenix_http_web_port, :integer,
+ default: 13_000,
+ changeset: fn changeset, key ->
+ Ecto.Changeset.validate_number(changeset, key,
+ greater_than: 0,
+ less_than_or_equal_to: 65_535
+ )
+ end
+ )
+
+ @doc """
+ Internal port to listen on for the Phoenix api server.
+ """
+ defconfig(:phoenix_http_api_port, :integer,
default: 13_000,
changeset: fn changeset, key ->
Ecto.Changeset.validate_number(changeset, key,
@@ -288,59 +276,54 @@ defmodule Domain.Config.Definitions do
dump: &Dumper.keyword/1
)
- ##############################################
- ## Admin Setup
- ##############################################
-
- @doc """
- Set this variable to `true` to create or reset the admin password every time Firezone
- starts. By default, the admin password is only set when Firezone is installed.
-
- Note: This **will not** change the status of local authentication.
- """
- defconfig(:reset_admin_on_boot, :boolean, default: false)
-
- @doc """
- Primary administrator email.
- """
- defconfig(:default_admin_email, :string,
- default: nil,
- sensitive: true,
- legacy_keys: [{:env, "ADMIN_EMAIL", "0.9"}],
- changeset: fn changeset, key ->
- changeset
- |> Domain.Validator.trim_change(key)
- |> Domain.Validator.validate_email(key)
- end
- )
-
- @doc """
- Default password that will be used for creating or resetting the primary administrator account.
- """
- defconfig(:default_admin_password, :string,
- default: nil,
- sensitive: true,
- changeset: fn changeset, key ->
- Ecto.Changeset.validate_length(changeset, key, min: 5)
- end
- )
-
##############################################
## Secrets
##############################################
@doc """
- Secret key used for signing JWTs.
+ Secret which is used to encode and sign auth tokens.
"""
- defconfig(:guardian_secret_key, :string,
+ defconfig(:auth_token_key_base, :string,
sensitive: true,
changeset: &Domain.Validator.validate_base64/2
)
@doc """
- Secret key used for encrypting sensitive data in the database.
+ Salt which is used to encode and sign auth tokens.
"""
- defconfig(:database_encryption_key, :string,
+ defconfig(:auth_token_salt, :string,
+ sensitive: true,
+ changeset: &Domain.Validator.validate_base64/2
+ )
+
+ @doc """
+ Secret which is used to encode and sign relays auth tokens.
+ """
+ defconfig(:relays_auth_token_key_base, :string,
+ sensitive: true,
+ changeset: &Domain.Validator.validate_base64/2
+ )
+
+ @doc """
+ Salt which is used to encode and sign relays auth tokens.
+ """
+ defconfig(:relays_auth_token_salt, :string,
+ sensitive: true,
+ changeset: &Domain.Validator.validate_base64/2
+ )
+
+ @doc """
+ Secret which is used to encode and sign gateways auth tokens.
+ """
+ defconfig(:gateways_auth_token_key_base, :string,
+ sensitive: true,
+ changeset: &Domain.Validator.validate_base64/2
+ )
+
+ @doc """
+ Salt which is used to encode and sign gateways auth tokens.
+ """
+ defconfig(:gateways_auth_token_salt, :string,
sensitive: true,
changeset: &Domain.Validator.validate_base64/2
)
@@ -382,88 +365,15 @@ defmodule Domain.Config.Definitions do
##############################################
@doc """
- Enable or disable management of devices on unprivileged accounts.
- """
- defconfig(:allow_unprivileged_device_management, :boolean, default: true)
-
- @doc """
- Enable or disable configuration of device network settings for unprivileged users.
- """
- defconfig(:allow_unprivileged_device_configuration, :boolean, default: true)
-
- @doc """
- Optionally require users to periodically authenticate to the Firezone web UI in order to keep their VPN sessions active.
- """
- defconfig(:vpn_session_duration, :integer,
- default: 0,
- changeset: fn changeset, key ->
- Ecto.Changeset.validate_number(changeset, key,
- greater_than_or_equal_to: 0,
- less_than_or_equal_to: 2_147_483_647
- )
- end
- )
-
- @doc """
- Interval for WireGuard [persistent keepalive](https://www.wireguard.com/quickstart/#nat-and-firewall-traversal-persistence).
-
- If you experience NAT or firewall traversal problems, you can enable this to send a keepalive packet every 25 seconds.
- Otherwise, keep it disabled with a 0 default value.
- """
- defconfig(:default_client_persistent_keepalive, :integer,
- default: 25,
- changeset: fn changeset, key ->
- Ecto.Changeset.validate_number(changeset, key,
- greater_than_or_equal_to: 0,
- less_than_or_equal_to: 120
- )
- end
- )
-
- @doc """
- WireGuard interface MTU for devices. 1280 is a safe bet for most networks.
- Leave this blank to omit this field from generated configs.
- """
- defconfig(:default_client_mtu, :integer,
- default: 1280,
- legacy_keys: [{:env, "WIREGUARD_MTU", "0.8"}],
- changeset: fn changeset, key ->
- Ecto.Changeset.validate_number(changeset, key,
- greater_than_or_equal_to: 576,
- less_than_or_equal_to: 1500
- )
- end
- )
-
- @doc """
- IPv4, IPv6 address, or FQDN that devices will be configured to connect to. Defaults to this server's FQDN.
- """
- defconfig(:default_client_endpoint, {:one_of, [Types.IPPort, :string]},
- default: fn ->
- external_uri = URI.parse(compile_config!(:external_url))
- wireguard_port = compile_config!(:wireguard_port)
- "#{external_uri.host}:#{wireguard_port}"
- end,
- changeset: fn
- Types.IPPort, changeset, _key ->
- changeset
-
- :string, changeset, key ->
- changeset
- |> Domain.Validator.trim_change(key)
- |> Domain.Validator.validate_fqdn(key, allow_port: true)
- end
- )
-
- @doc """
- Comma-separated list of DNS servers to use for devices.
+ Comma-separated list of upstream DNS servers to use for devices.
It can be either an IP address or a FQDN if you intend to use a DNS-over-TLS server.
- Leave this blank to omit the `DNS` section from generated configs.
+ Leave this blank to omit the `DNS` section from generated configs,
+ which will make devices use default system-provided DNS even when VPN session is active.
"""
defconfig(
- :default_client_dns,
+ :devices_upstream_dns,
{:array, ",", {:one_of, [Types.IP, :string]}, validate_unique: true},
default: [],
changeset: fn
@@ -477,45 +387,27 @@ defmodule Domain.Config.Definitions do
end
)
+ ##############################################
+ ## Userpass / SAML / OIDC / Magic Link authentication
+ ##############################################
+
@doc """
- Configures the default AllowedIPs setting for devices.
+ Enable or disable the authentication methods for all users.
- AllowedIPs determines which destination IPs get routed through Firezone.
-
- Specify a comma-separated list of IPs or CIDRs here to achieve split tunneling, or use
- `0.0.0.0/0, ::/0` to route all device traffic through this Firezone server.
+ It will affect on which auth providers can be created per an account but will not disable
+ already active providers when setting is changed.
"""
defconfig(
- :default_client_allowed_ips,
- {:array, ",", {:one_of, [Types.CIDR, Types.IP]}, validate_unique: true},
- default: "0.0.0.0/0, ::/0"
+ :auth_provider_adapters,
+ {
+ :array,
+ ",",
+ {:parameterized, Ecto.Enum,
+ Ecto.Enum.init(values: ~w[email openid_connect userpass token]a)}
+ },
+ default: ~w[email openid_connect token]a
)
- ##############################################
- ## Limits
- ##############################################
-
- defconfig(:max_devices_per_user, :integer,
- default: 10,
- changeset: fn changeset, key ->
- Ecto.Changeset.validate_number(changeset, key,
- greater_than_or_equal_to: 0,
- less_than_or_equal_to: 100
- )
- end
- )
-
- ##############################################
- ## Userpass / SAML / OIDC authentication
- ##############################################
-
- @doc """
- Enable or disable the local authentication method for all users.
- """
- # XXX: This should be replaced with auth_methods config which accepts a list
- # of enabled methods.
- defconfig(:local_auth_enabled, :boolean, default: true)
-
##############################################
## Telemetry
##############################################
@@ -536,91 +428,11 @@ defmodule Domain.Config.Definitions do
)
##############################################
- ## Connectivity Checks
+ ## Gateways
##############################################
- @doc """
- Enable / disable periodic checking for egress connectivity. Determines the instance's public IP to populate `Endpoint` fields.
- """
- defconfig(:connectivity_checks_enabled, :boolean, default: true)
-
- @doc """
- Periodicity in seconds to check for egress connectivity.
- """
- defconfig(:connectivity_checks_interval, :integer,
- default: 43_200,
- changeset: fn changeset, key ->
- Ecto.Changeset.validate_number(changeset, key,
- greater_than_or_equal_to: 60,
- less_than_or_equal_to: 86_400
- )
- end
- )
-
- ##############################################
- ## WireGuard
- ##############################################
-
- @doc """
- A port on which WireGuard will listen for incoming connections.
- """
- defconfig(:wireguard_port, :integer,
- default: 51_820,
- changeset: fn changeset, key ->
- Ecto.Changeset.validate_number(changeset, key,
- greater_than: 0,
- less_than_or_equal_to: 65_535
- )
- end
- )
-
- @doc """
- Enable or disable IPv4 support for WireGuard.
- """
- defconfig(:wireguard_ipv4_enabled, :boolean, default: true)
- defconfig(:wireguard_ipv4_masquerade, :boolean, default: true)
-
- defconfig(:wireguard_ipv4_network, Types.CIDR,
- default: "10.3.2.0/24",
- changeset: &Domain.Validator.validate_ip_type_inclusion(&1, &2, [:ipv4])
- )
-
- defconfig(:wireguard_ipv4_address, Types.IP,
- default: "10.3.2.1",
- changeset: &Domain.Validator.validate_ip_type_inclusion(&1, &2, [:ipv4])
- )
-
- @doc """
- Enable or disable IPv6 support for WireGuard.
- """
- defconfig(:wireguard_ipv6_enabled, :boolean, default: true)
- defconfig(:wireguard_ipv6_masquerade, :boolean, default: true)
-
- defconfig(:wireguard_ipv6_network, Types.CIDR,
- default: "fd00::3:2:0/120",
- changeset: &Domain.Validator.validate_ip_type_inclusion(&1, &2, [:ipv6])
- )
-
- defconfig(:wireguard_ipv6_address, Types.IP,
- default: "fd00::3:2:1",
- changeset: &Domain.Validator.validate_ip_type_inclusion(&1, &2, [:ipv6])
- )
-
- defconfig(:wireguard_private_key_path, :string,
- default: "/var/firezone/private_key"
- # We don't check if the file exists, because it is generated on
- # the first boot.
- # changeset: &Domain.Validator.validate_file(&1, &2)
- )
-
- defconfig(:wireguard_interface_name, :string, default: "wg-firezone")
-
- defconfig(:gateway_egress_interface, :string,
- legacy_keys: [{:env, "EGRESS_INTERFACE", "0.8"}],
- default: "eth0"
- )
-
- defconfig(:gateway_nft_path, :string, default: "nft", legacy_keys: [{:env, "NFT_PATH", "0.8"}])
+ defconfig(:gateway_ipv4_masquerade, :boolean, default: true)
+ defconfig(:gateway_ipv6_masquerade, :boolean, default: true)
##############################################
## HTTP Client Settings
diff --git a/apps/domain/lib/domain/devices.ex b/apps/domain/lib/domain/devices.ex
new file mode 100644
index 000000000..17af1c6bb
--- /dev/null
+++ b/apps/domain/lib/domain/devices.ex
@@ -0,0 +1,178 @@
+defmodule Domain.Devices do
+ use Supervisor
+ alias Domain.{Repo, Auth, Validator}
+ alias Domain.Actors
+ alias Domain.Devices.{Device, Authorizer, Presence}
+
+ def start_link(opts) do
+ Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
+ end
+
+ def init(_opts) do
+ children = [
+ Presence
+ ]
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+
+ def count_by_account_id(account_id) do
+ Device.Query.by_account_id(account_id)
+ |> Repo.aggregate(:count)
+ end
+
+ def count_by_actor_id(actor_id) do
+ Device.Query.by_actor_id(actor_id)
+ |> Repo.aggregate(:count)
+ end
+
+ def fetch_device_by_id(id, %Auth.Subject{} = subject) do
+ required_permissions =
+ {:one_of,
+ [
+ Authorizer.manage_devices_permission(),
+ Authorizer.manage_own_devices_permission()
+ ]}
+
+ with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
+ true <- Validator.valid_uuid?(id) do
+ Device.Query.by_id(id)
+ |> Authorizer.for_subject(subject)
+ |> Repo.fetch()
+ else
+ false -> {:error, :not_found}
+ other -> other
+ end
+ end
+
+ def fetch_device_by_id!(id, opts \\ []) do
+ {preload, _opts} = Keyword.pop(opts, :preload, [])
+
+ Device.Query.by_id(id)
+ |> Repo.one!()
+ |> Repo.preload(preload)
+ end
+
+ def list_devices(%Auth.Subject{} = subject) do
+ required_permissions =
+ {:one_of,
+ [
+ Authorizer.manage_devices_permission(),
+ Authorizer.manage_own_devices_permission()
+ ]}
+
+ with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do
+ Device.Query.all()
+ |> Authorizer.for_subject(subject)
+ |> Repo.list()
+ end
+ end
+
+ def list_devices_for_actor(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
+ list_devices_by_actor_id(actor.id, subject)
+ end
+
+ def list_devices_by_actor_id(actor_id, %Auth.Subject{} = subject) do
+ required_permissions =
+ {:one_of,
+ [
+ Authorizer.manage_devices_permission(),
+ Authorizer.manage_own_devices_permission()
+ ]}
+
+ with :ok <- Auth.ensure_has_permissions(subject, required_permissions),
+ true <- Validator.valid_uuid?(actor_id) do
+ Device.Query.by_actor_id(actor_id)
+ |> Authorizer.for_subject(subject)
+ |> Repo.list()
+ else
+ false -> {:error, :not_found}
+ other -> other
+ end
+ end
+
+ def change_device(%Device{} = device, attrs \\ %{}) do
+ Device.Changeset.update_changeset(device, attrs)
+ end
+
+ def upsert_device(attrs \\ %{}, %Auth.Subject{identity: %Auth.Identity{} = identity} = subject) do
+ with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_own_devices_permission()) do
+ changeset = Device.Changeset.upsert_changeset(identity, subject.context, attrs)
+
+ Ecto.Multi.new()
+ |> Ecto.Multi.insert(:device, changeset,
+ conflict_target: Device.Changeset.upsert_conflict_target(),
+ on_conflict: Device.Changeset.upsert_on_conflict(),
+ returning: true
+ )
+ |> resolve_address_multi(:ipv4)
+ |> resolve_address_multi(:ipv6)
+ |> Ecto.Multi.update(:device_with_address, fn
+ %{device: %Device{} = device, ipv4: ipv4, ipv6: ipv6} ->
+ Device.Changeset.finalize_upsert_changeset(device, ipv4, ipv6)
+ end)
+ |> Repo.transaction()
+ |> case do
+ {:ok, %{device_with_address: device}} -> {:ok, device}
+ {:error, :device, changeset, _effects_so_far} -> {:error, changeset}
+ end
+ end
+ end
+
+ defp resolve_address_multi(multi, type) do
+ Ecto.Multi.run(multi, type, fn _repo, %{device: %Device{} = device} ->
+ if address = Map.get(device, type) do
+ {:ok, address}
+ else
+ {:ok, Domain.Network.fetch_next_available_address!(device.account_id, type)}
+ end
+ end)
+ end
+
+ def update_device(%Device{} = device, attrs, %Auth.Subject{} = subject) do
+ with :ok <- authorize_actor_device_management(device.actor_id, subject) do
+ Device.Query.by_id(device.id)
+ |> Authorizer.for_subject(subject)
+ |> Repo.fetch_and_update(with: &Device.Changeset.update_changeset(&1, attrs))
+ end
+ end
+
+ def delete_device(%Device{} = device, %Auth.Subject{} = subject) do
+ with :ok <- authorize_actor_device_management(device.actor_id, subject) do
+ Device.Query.by_id(device.id)
+ |> Authorizer.for_subject(subject)
+ |> Repo.fetch_and_update(with: &Device.Changeset.delete_changeset/1)
+ end
+ end
+
+ def authorize_actor_device_management(%Actors.Actor{} = actor, %Auth.Subject{} = subject) do
+ authorize_actor_device_management(actor.id, subject)
+ end
+
+ def authorize_actor_device_management(actor_id, %Auth.Subject{actor: %{id: actor_id}} = subject) do
+ Auth.ensure_has_permissions(subject, Authorizer.manage_own_devices_permission())
+ end
+
+ def authorize_actor_device_management(_actor_id, %Auth.Subject{} = subject) do
+ Auth.ensure_has_permissions(subject, Authorizer.manage_devices_permission())
+ end
+
+ def connect_device(%Device{} = device) do
+ Phoenix.PubSub.subscribe(Domain.PubSub, "actor:#{device.actor_id}")
+
+ {:ok, _} =
+ Presence.track(self(), "devices", device.id, %{
+ online_at: System.system_time(:second)
+ })
+
+ :ok
+ end
+
+ def fetch_device_config!(%Device{} = device) do
+ %{
+ devices_upstream_dns: upstream_dns
+ } = Domain.Config.fetch_resolved_configs!(device.account_id, [:devices_upstream_dns])
+
+ [upstream_dns: upstream_dns]
+ end
+end
diff --git a/apps/domain/lib/domain/devices/authorizer.ex b/apps/domain/lib/domain/devices/authorizer.ex
new file mode 100644
index 000000000..d9af0c477
--- /dev/null
+++ b/apps/domain/lib/domain/devices/authorizer.ex
@@ -0,0 +1,39 @@
+defmodule Domain.Devices.Authorizer do
+ use Domain.Auth.Authorizer
+ alias Domain.Devices.Device
+
+ def manage_own_devices_permission, do: build(Device, :manage_own)
+ def manage_devices_permission, do: build(Device, :manage)
+
+ @impl Domain.Auth.Authorizer
+
+ def list_permissions_for_role(:account_admin_user) do
+ [
+ manage_own_devices_permission(),
+ manage_devices_permission()
+ ]
+ end
+
+ def list_permissions_for_role(:account_user) do
+ [
+ manage_own_devices_permission()
+ ]
+ end
+
+ def list_permissions_for_role(_) do
+ []
+ end
+
+ @impl Domain.Auth.Authorizer
+ def for_subject(queryable, %Subject{} = subject) do
+ cond do
+ has_permission?(subject, manage_devices_permission()) ->
+ Device.Query.by_account_id(queryable, subject.account.id)
+
+ has_permission?(subject, manage_own_devices_permission()) ->
+ queryable
+ |> Device.Query.by_account_id(subject.account.id)
+ |> Device.Query.by_actor_id(subject.actor.id)
+ end
+ end
+end
diff --git a/apps/domain/lib/domain/clients/client.ex b/apps/domain/lib/domain/devices/device.ex
similarity index 90%
rename from apps/domain/lib/domain/clients/client.ex
rename to apps/domain/lib/domain/devices/device.ex
index 98102fff4..428d54f7c 100644
--- a/apps/domain/lib/domain/clients/client.ex
+++ b/apps/domain/lib/domain/devices/device.ex
@@ -1,7 +1,7 @@
-defmodule Domain.Clients.Client do
+defmodule Domain.Devices.Device do
use Domain, :schema
- schema "clients" do
+ schema "devices" do
field :external_id, :string
field :name, :string
diff --git a/apps/domain/lib/domain/clients/client/changeset.ex b/apps/domain/lib/domain/devices/device/changeset.ex
similarity index 80%
rename from apps/domain/lib/domain/clients/client/changeset.ex
rename to apps/domain/lib/domain/devices/device/changeset.ex
index 0ba4f2abd..8a4891214 100644
--- a/apps/domain/lib/domain/clients/client/changeset.ex
+++ b/apps/domain/lib/domain/devices/device/changeset.ex
@@ -1,7 +1,7 @@
-defmodule Domain.Clients.Client.Changeset do
+defmodule Domain.Devices.Device.Changeset do
use Domain, :changeset
alias Domain.{Version, Auth}
- alias Domain.Clients
+ alias Domain.Devices
@upsert_fields ~w[external_id name public_key]a
@conflict_replace_fields ~w[public_key
@@ -19,7 +19,7 @@ defmodule Domain.Clients.Client.Changeset do
def upsert_on_conflict, do: {:replace, @conflict_replace_fields}
def upsert_changeset(%Auth.Identity{} = identity, %Auth.Context{} = context, attrs) do
- %Clients.Client{}
+ %Devices.Device{}
|> cast(attrs, @upsert_fields)
|> put_default_value(:name, &generate_name/0)
|> put_change(:identity_id, identity.id)
@@ -31,30 +31,30 @@ defmodule Domain.Clients.Client.Changeset do
|> validate_required(@required_fields)
|> validate_base64(:public_key)
|> validate_length(:public_key, is: @key_length)
- |> unique_constraint(:ipv4, name: :clients_account_id_ipv4_index)
- |> unique_constraint(:ipv6, name: :clients_account_id_ipv6_index)
+ |> unique_constraint(:ipv4, name: :devices_account_id_ipv4_index)
+ |> unique_constraint(:ipv6, name: :devices_account_id_ipv6_index)
|> put_change(:last_seen_at, DateTime.utc_now())
- |> put_client_version()
+ |> put_device_version()
end
- def finalize_upsert_changeset(%Clients.Client{} = client, ipv4, ipv6) do
- client
+ def finalize_upsert_changeset(%Devices.Device{} = device, ipv4, ipv6) do
+ device
|> change()
|> put_change(:ipv4, ipv4)
|> put_change(:ipv6, ipv6)
- |> unique_constraint(:ipv4, name: :clients_account_id_ipv4_index)
- |> unique_constraint(:ipv6, name: :clients_account_id_ipv6_index)
+ |> unique_constraint(:ipv4, name: :devices_account_id_ipv4_index)
+ |> unique_constraint(:ipv6, name: :devices_account_id_ipv6_index)
end
- def update_changeset(%Clients.Client{} = client, attrs) do
- client
+ def update_changeset(%Devices.Device{} = device, attrs) do
+ device
|> cast(attrs, @update_fields)
|> changeset()
|> validate_required(@required_fields)
end
- def delete_changeset(%Clients.Client{} = client) do
- client
+ def delete_changeset(%Devices.Device{} = device) do
+ device
|> change()
|> put_default_value(:deleted_at, DateTime.utc_now())
end
@@ -69,7 +69,7 @@ defmodule Domain.Clients.Client.Changeset do
|> unique_constraint(:external_id)
end
- defp put_client_version(changeset) do
+ defp put_device_version(changeset) do
with {_data_or_changes, user_agent} when not is_nil(user_agent) <-
fetch_field(changeset, :last_seen_user_agent),
{:ok, version} <- Version.fetch_version(user_agent) do
diff --git a/apps/domain/lib/domain/devices/device/query.ex b/apps/domain/lib/domain/devices/device/query.ex
new file mode 100644
index 000000000..b3b0c98bb
--- /dev/null
+++ b/apps/domain/lib/domain/devices/device/query.ex
@@ -0,0 +1,40 @@
+defmodule Domain.Devices.Device.Query do
+ use Domain, :query
+
+ def all do
+ from(devices in Domain.Devices.Device, as: :devices)
+ |> where([devices: devices], is_nil(devices.deleted_at))
+ end
+
+ def by_id(queryable \\ all(), id) do
+ where(queryable, [devices: devices], devices.id == ^id)
+ end
+
+ def by_actor_id(queryable \\ all(), actor_id) do
+ where(queryable, [devices: devices], devices.actor_id == ^actor_id)
+ end
+
+ def by_account_id(queryable \\ all(), account_id) do
+ where(queryable, [devices: devices], devices.account_id == ^account_id)
+ end
+
+ def returning_all(queryable \\ all()) do
+ select(queryable, [devices: devices], devices)
+ end
+
+ def with_preloaded_actor(queryable \\ all()) do
+ with_named_binding(queryable, :actor, fn queryable, binding ->
+ queryable
+ |> join(:inner, [devices: devices], actor in assoc(devices, ^binding), as: ^binding)
+ |> preload([devices: devices, actor: actor], actor: actor)
+ end)
+ end
+
+ def with_preloaded_identity(queryable \\ all()) do
+ with_named_binding(queryable, :identity, fn queryable, binding ->
+ queryable
+ |> join(:inner, [devices: devices], identity in assoc(devices, ^binding), as: ^binding)
+ |> preload([devices: devices, identity: identity], identity: identity)
+ end)
+ end
+end
diff --git a/apps/domain/lib/domain/clients/presence.ex b/apps/domain/lib/domain/devices/presence.ex
similarity index 69%
rename from apps/domain/lib/domain/clients/presence.ex
rename to apps/domain/lib/domain/devices/presence.ex
index b0bf4f34d..1232022ed 100644
--- a/apps/domain/lib/domain/clients/presence.ex
+++ b/apps/domain/lib/domain/devices/presence.ex
@@ -1,4 +1,4 @@
-defmodule Domain.Clients.Presence do
+defmodule Domain.Devices.Presence do
use Phoenix.Presence,
otp_app: :domain,
pubsub_server: Domain.PubSub
diff --git a/apps/domain/lib/domain/gateways.ex b/apps/domain/lib/domain/gateways.ex
index 5e55e04ab..1e7a88487 100644
--- a/apps/domain/lib/domain/gateways.ex
+++ b/apps/domain/lib/domain/gateways.ex
@@ -199,6 +199,28 @@ defmodule Domain.Gateways do
end
end
+ def encode_token!(%Token{value: value} = token) when not is_nil(value) do
+ body = {token.id, token.value}
+ config = fetch_config!()
+ key_base = Keyword.fetch!(config, :key_base)
+ salt = Keyword.fetch!(config, :salt)
+ Plug.Crypto.sign(key_base, salt, body)
+ end
+
+ def authorize_gateway(encrypted_secret) do
+ config = fetch_config!()
+ key_base = Keyword.fetch!(config, :key_base)
+ salt = Keyword.fetch!(config, :salt)
+
+ with {:ok, {id, secret}} <- Plug.Crypto.verify(key_base, salt, encrypted_secret),
+ {:ok, token} <- use_token_by_id_and_secret(id, secret) do
+ {:ok, token}
+ else
+ {:error, :invalid} -> {:error, :invalid_token}
+ {:error, :not_found} -> {:error, :invalid_token}
+ end
+ end
+
def connect_gateway(%Gateway{} = gateway) do
{:ok, _} =
Presence.track(self(), "gateways", gateway.id, %{
@@ -207,4 +229,12 @@ defmodule Domain.Gateways do
:ok
end
+
+ def fetch_gateway_config!(%Gateway{} = _gateway) do
+ Application.fetch_env!(:domain, __MODULE__)
+ end
+
+ defp fetch_config! do
+ Domain.Config.fetch_env!(:domain, __MODULE__)
+ end
end
diff --git a/apps/domain/lib/domain/gateways/authorizer.ex b/apps/domain/lib/domain/gateways/authorizer.ex
index 224629dae..aa98d0355 100644
--- a/apps/domain/lib/domain/gateways/authorizer.ex
+++ b/apps/domain/lib/domain/gateways/authorizer.ex
@@ -6,7 +6,7 @@ defmodule Domain.Gateways.Authorizer do
@impl Domain.Auth.Authorizer
- def list_permissions_for_role(:admin) do
+ def list_permissions_for_role(:account_admin_user) do
[
manage_gateways_permission()
]
diff --git a/apps/domain/lib/domain/relays.ex b/apps/domain/lib/domain/relays.ex
index bdd6affa4..9c14c8dc4 100644
--- a/apps/domain/lib/domain/relays.ex
+++ b/apps/domain/lib/domain/relays.ex
@@ -183,6 +183,28 @@ defmodule Domain.Relays do
end
end
+ def encode_token!(%Token{value: value} = token) when not is_nil(value) do
+ body = {token.id, token.value}
+ config = fetch_config!()
+ key_base = Keyword.fetch!(config, :key_base)
+ salt = Keyword.fetch!(config, :salt)
+ Plug.Crypto.sign(key_base, salt, body)
+ end
+
+ def authorize_relay(encrypted_secret) do
+ config = fetch_config!()
+ key_base = Keyword.fetch!(config, :key_base)
+ salt = Keyword.fetch!(config, :salt)
+
+ with {:ok, {id, secret}} <- Plug.Crypto.verify(key_base, salt, encrypted_secret),
+ {:ok, token} <- use_token_by_id_and_secret(id, secret) do
+ {:ok, token}
+ else
+ {:error, :invalid} -> {:error, :invalid_token}
+ {:error, :not_found} -> {:error, :invalid_token}
+ end
+ end
+
def connect_relay(%Relay{} = relay, secret) do
{:ok, _} =
Presence.track(self(), "relays", relay.id, %{
@@ -192,4 +214,8 @@ defmodule Domain.Relays do
:ok
end
+
+ defp fetch_config! do
+ Domain.Config.fetch_env!(:domain, __MODULE__)
+ end
end
diff --git a/apps/domain/lib/domain/relays/authorizer.ex b/apps/domain/lib/domain/relays/authorizer.ex
index f84a155c0..fb757091b 100644
--- a/apps/domain/lib/domain/relays/authorizer.ex
+++ b/apps/domain/lib/domain/relays/authorizer.ex
@@ -6,7 +6,7 @@ defmodule Domain.Relays.Authorizer do
@impl Domain.Auth.Authorizer
- def list_permissions_for_role(:admin) do
+ def list_permissions_for_role(:account_admin_user) do
[
manage_relays_permission()
]
diff --git a/apps/domain/lib/domain/release.ex b/apps/domain/lib/domain/release.ex
index d4951e2d1..6bb6a8705 100644
--- a/apps/domain/lib/domain/release.ex
+++ b/apps/domain/lib/domain/release.ex
@@ -1,8 +1,6 @@
defmodule Domain.Release do
- # alias Domain.{ApiTokens, Users}
require Logger
- # @app :domain
@repos Application.compile_env!(:domain, :ecto_repos)
def migrate do
@@ -10,92 +8,4 @@ defmodule Domain.Release do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
-
- # def create_admin_user do
- # start_domain_app()
-
- # email = email()
-
- # with {:ok, _user} <- Users.fetch_user_by_email(email) do
- # change_password(email(), default_password())
- # {:ok, user} = reset_role(email(), :admin)
-
- # # Notify the user
- # Logger.info(
- # "Password for user specified by DEFAULT_ADMIN_EMAIL reset to DEFAULT_ADMIN_PASSWORD!"
- # )
-
- # {:ok, user}
- # else
- # {:error, :not_found} ->
- # with {:ok, user} <-
- # Users.create_user(:admin, %{
- # email: email(),
- # password: default_password(),
- # password_confirmation: default_password()
- # }) do
- # # Notify the user
- # Logger.info(
- # "An admin user specified by DEFAULT_ADMIN_EMAIL is created with a DEFAULT_ADMIN_PASSWORD!"
- # )
-
- # {:ok, user}
- # else
- # {:error, changeset} ->
- # Logger.error("Failed to create admin user: #{inspect(changeset.errors)}")
- # {:error, changeset}
- # end
- # end
- # end
-
- # def create_api_token(device \\ :stdio) do
- # start_domain_app()
-
- # device
- # |> IO.write(default_admin_user() |> mint_jwt())
- # end
-
- # def change_password(email, password) do
- # params = %{
- # "password" => password,
- # "password_confirmation" => password
- # }
-
- # {:ok, user} = Users.fetch_user_by_email(email)
- # {:ok, _user} = Users.update_user(user, params)
- # end
-
- # def reset_role(email, role) do
- # {:ok, user} = Users.fetch_user_by_email(email)
- # Users.update_user(user, %{role: role})
- # end
-
- # defp email do
- # Domain.Config.fetch_env!(:domain, :admin_email)
- # end
-
- # defp default_admin_user do
- # case Users.fetch_user_by_email(email()) do
- # {:ok, user} -> user
- # {:error, :not_found} -> nil
- # end
- # end
-
- # defp mint_jwt(%Users.User{} = user) do
- # {:ok, api_token} = ApiTokens.create_api_token(user, %{})
- # {:ok, secret, _claims} = Web.Auth.JSON.Authentication.fz_encode_and_sign(api_token)
- # secret
- # end
-
- # defp start_domain_app do
- # # Load the app
- # :ok = Application.ensure_loaded(@app)
-
- # # Start the app dependencies
- # {:ok, _apps} = Application.ensure_all_started(@app)
- # end
-
- # defp default_password do
- # Domain.Config.fetch_env!(:domain, :default_admin_password)
- # end
end
diff --git a/apps/domain/lib/domain/resources.ex b/apps/domain/lib/domain/resources.ex
index 8d899763e..f4c28082c 100644
--- a/apps/domain/lib/domain/resources.ex
+++ b/apps/domain/lib/domain/resources.ex
@@ -51,7 +51,7 @@ defmodule Domain.Resources do
# {:ok, actors} = list_authorized_actors(resource)
# Phoenix.PubSub.broadcast(
# Domain.PubSub,
- # "actor_client:#{subject.actor.id}",
+ # "actor_device:#{subject.actor.id}",
# {:resource_added, resource.id}
# )
@@ -82,7 +82,7 @@ defmodule Domain.Resources do
{:ok, resource} ->
# Phoenix.PubSub.broadcast(
# Domain.PubSub,
- # "actor_client:#{resource.actor_id}",
+ # "actor_device:#{resource.actor_id}",
# {:resource_updated, resource.id}
# )
@@ -103,7 +103,7 @@ defmodule Domain.Resources do
{:ok, resource} ->
# Phoenix.PubSub.broadcast(
# Domain.PubSub,
- # "actor_client:#{resource.actor_id}",
+ # "actor_device:#{resource.actor_id}",
# {:resource_removed, resource.id}
# )
diff --git a/apps/domain/lib/domain/resources/authorizer.ex b/apps/domain/lib/domain/resources/authorizer.ex
index b5f12c74c..83bac9db9 100644
--- a/apps/domain/lib/domain/resources/authorizer.ex
+++ b/apps/domain/lib/domain/resources/authorizer.ex
@@ -5,7 +5,7 @@ defmodule Domain.Resources.Authorizer do
def manage_resources_permission, do: build(Resource, :manage)
@impl Domain.Auth.Authorizer
- def list_permissions_for_role(:admin) do
+ def list_permissions_for_role(:account_admin_user) do
[
manage_resources_permission()
]
diff --git a/apps/domain/lib/domain/sandbox.ex b/apps/domain/lib/domain/sandbox.ex
index 4f6381932..e0452c4a1 100644
--- a/apps/domain/lib/domain/sandbox.ex
+++ b/apps/domain/lib/domain/sandbox.ex
@@ -12,7 +12,7 @@ defmodule Domain.Sandbox do
sandbox.allow(metadata, Ecto.Adapters.SQL.Sandbox)
end
else
- def allow(_metadata) do
+ def allow(_sandbox, _metadata) do
:ok
end
end
diff --git a/apps/domain/lib/domain/telemetry.ex b/apps/domain/lib/domain/telemetry.ex
index 754005505..1cdc7c22d 100644
--- a/apps/domain/lib/domain/telemetry.ex
+++ b/apps/domain/lib/domain/telemetry.ex
@@ -1,194 +1,189 @@
-defmodule Domain.Telemetry do
- @moduledoc """
- Functions for various telemetry events.
- """
- use Supervisor
- alias Domain.Telemetry.{Timer, PostHog}
- require Logger
+# TODO: when app starts for migrations set env to disable connectivity checks and telemetry
+# defmodule Domain.Telemetry do
+# @moduledoc """
+# Functions for various telemetry events.
+# """
+# use Supervisor
+# alias Domain.Telemetry.{Timer, PostHog}
+# require Logger
- def start_link(opts) do
- Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
- end
+# def start_link(opts) do
+# Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
+# end
- def init(_opts) do
- config = Domain.Config.fetch_env!(:domain, Domain.Telemetry)
+# def init(_opts) do
+# config = Domain.Config.fetch_env!(:domain, Domain.Telemetry)
- if Keyword.fetch!(config, :enabled) == true do
- children = [Timer]
- Supervisor.init(children, strategy: :one_for_one)
- else
- :ignore
- end
- end
+# if Keyword.fetch!(config, :enabled) == true do
+# children = [Timer]
+# Supervisor.init(children, strategy: :one_for_one)
+# else
+# :ignore
+# end
+# end
- def create_api_token do
- PostHog.capture("add_api_token", common_fields())
- :ok
- end
+# def create_api_token do
+# PostHog.capture("add_api_token", common_fields())
+# :ok
+# end
- def delete_api_token(api_token) do
- PostHog.capture(
- "delete_api_token",
- common_fields() ++
- [
- api_token_created_at: api_token.inserted_at
- ]
- )
+# def delete_api_token(api_token) do
+# PostHog.capture(
+# "delete_api_token",
+# common_fields() ++
+# [
+# api_token_created_at: api_token.inserted_at
+# ]
+# )
- :ok
- end
+# :ok
+# end
- def add_device do
- PostHog.capture("add_device", common_fields())
- :ok
- end
+# def add_device do
+# PostHog.capture("add_device", common_fields())
+# :ok
+# end
- def add_actor do
- PostHog.capture("add_actor", common_fields())
- :ok
- end
+# def add_actor do
+# PostHog.capture("add_actor", common_fields())
+# :ok
+# end
- def add_rule do
- PostHog.capture("add_rule", common_fields())
- :ok
- end
+# def add_rule do
+# PostHog.capture("add_rule", common_fields())
+# :ok
+# end
- def delete_device do
- PostHog.capture("delete_device", common_fields())
- :ok
- end
+# def delete_device do
+# PostHog.capture("delete_device", common_fields())
+# :ok
+# end
- def delete_actor do
- PostHog.capture("delete_actor", common_fields())
- :ok
- end
+# def delete_actor do
+# PostHog.capture("delete_actor", common_fields())
+# :ok
+# end
- def delete_rule do
- PostHog.capture("delete_rule", common_fields())
- :ok
- end
+# def delete_rule do
+# PostHog.capture("delete_rule", common_fields())
+# :ok
+# end
- def login do
- PostHog.capture("login", common_fields())
- :ok
- end
+# def login do
+# PostHog.capture("login", common_fields())
+# :ok
+# end
- def enable_actor do
- PostHog.capture("enable_actor", common_fields())
- :ok
- end
+# def enable_actor do
+# PostHog.capture("enable_actor", common_fields())
+# :ok
+# end
- def disable_actor do
- PostHog.capture("disable_actor", common_fields())
- :ok
- end
+# def disable_actor do
+# PostHog.capture("disable_actor", common_fields())
+# :ok
+# end
- def domain_started do
- PostHog.capture("domain_started", common_fields())
- :ok
- end
+# def domain_started do
+# PostHog.capture("domain_started", common_fields())
+# :ok
+# end
- def ping do
- PostHog.capture("ping", ping_data())
- :ok
- end
+# def ping do
+# PostHog.capture("ping", ping_data())
+# :ok
+# end
- # How far back to count handshakes as an active device
- # @active_device_window 86_400
- def ping_data do
- %{
- allow_unprivileged_device_management: {_, allow_unprivileged_device_management},
- allow_unprivileged_device_configuration: {_, allow_unprivileged_device_configuration},
- local_auth_enabled: {_, local_auth_enabled},
- logo: {_, logo}
- } =
- Domain.Config.fetch_source_and_configs!([
- :allow_unprivileged_device_management,
- :allow_unprivileged_device_configuration,
- :local_auth_enabled,
- :logo
- ])
+# # How far back to count handshakes as an active device
+# # @active_device_window 86_400
+# def ping_data do
+# %{
+# local_auth_enabled: {_, local_auth_enabled},
+# logo: {_, logo}
+# } =
+# Domain.Config.fetch_resolved_configs_with_sources!([
+# :local_auth_enabled,
+# :logo
+# ])
- common_fields() ++
- [
- # devices_active_within_24h: Devices.count_active_within(@active_device_window),
- # admin_count: Users.count_by_role(:admin),
- # actor_count: Users.count(),
- in_docker: in_docker?(),
- # device_count: Devices.count(),
- # max_devices_for_actors: Devices.count_maximum_for_a_actor(),
- # actors_with_mfa: MFA.count_actors_with_mfa_enabled(),
- # actors_with_mfa_totp: MFA.count_actors_with_totp_method(),
- unprivileged_device_management: allow_unprivileged_device_management,
- unprivileged_device_configuration: allow_unprivileged_device_configuration,
- local_authentication: local_auth_enabled,
- # outbound_email: Web.Mailer.active?(),
- external_database:
- external_database?(Map.new(Domain.Config.fetch_env!(:domain, Domain.Repo))),
- logo_type: Domain.Config.Logo.type(logo)
- ]
- end
+# common_fields() ++
+# [
+# # devices_active_within_24h: Devices.count_active_within(@active_device_window),
+# # admin_count: Users.count_by_role(:account_admin_user),
+# # actor_count: Users.count(),
+# in_docker: in_docker?(),
+# # device_count: Devices.count(),
+# # max_devices_for_actors: Devices.count_maximum_for_a_actor(),
+# # actors_with_mfa: MFA.count_actors_with_mfa_enabled(),
+# # actors_with_mfa_totp: MFA.count_actors_with_totp_method(),
+# local_authentication: local_auth_enabled,
+# # outbound_email: Web.Mailer.active?(),
+# external_database:
+# external_database?(Map.new(Domain.Config.fetch_env!(:domain, Domain.Repo))),
+# logo_type: Domain.Config.Logo.type(logo)
+# ]
+# end
- defp in_docker? do
- File.exists?("/.dockerenv")
- end
+# defp in_docker? do
+# File.exists?("/.dockerenv")
+# end
- defp common_fields do
- [
- distinct_id: id(),
- fqdn: fqdn(),
- version: version(),
- kernel_version: "#{os_type()} #{os_version()}"
- ]
- end
+# defp common_fields do
+# [
+# distinct_id: id(),
+# fqdn: fqdn(),
+# version: version(),
+# kernel_version: "#{os_type()} #{os_version()}"
+# ]
+# end
- def id do
- Domain.Config.fetch_env!(:domain, __MODULE__)
- |> Keyword.fetch!(:id)
- end
+# def id do
+# Domain.Config.fetch_env!(:domain, __MODULE__)
+# |> Keyword.fetch!(:id)
+# end
- defp fqdn do
- :web
- |> Domain.Config.fetch_env!(Web.Endpoint)
- |> Keyword.get(:url)
- |> Keyword.get(:host)
- end
+# defp fqdn do
+# :web
+# |> Domain.Config.fetch_env!(Web.Endpoint)
+# |> Keyword.get(:url)
+# |> Keyword.get(:host)
+# end
- defp version do
- Application.spec(:domain, :vsn) |> to_string()
- end
+# defp version do
+# Application.spec(:domain, :vsn) |> to_string()
+# end
- defp external_database?(repo_conf) when is_map_key(repo_conf, :hostname) do
- is_external_db?(repo_conf.hostname)
- end
+# defp external_database?(repo_conf) when is_map_key(repo_conf, :hostname) do
+# is_external_db?(repo_conf.hostname)
+# end
- defp external_database?(repo_conf) when is_map_key(repo_conf, :url) do
- %{host: host} = URI.parse(repo_conf.url)
+# defp external_database?(repo_conf) when is_map_key(repo_conf, :url) do
+# %{host: host} = URI.parse(repo_conf.url)
- is_external_db?(host)
- end
+# is_external_db?(host)
+# end
- defp is_external_db?(host) do
- host != "localhost" && host != "127.0.0.1"
- end
+# defp is_external_db?(host) do
+# host != "localhost" && host != "127.0.0.1"
+# end
- defp os_type do
- case :os.type() do
- {:unix, type} ->
- "#{type}"
+# defp os_type do
+# case :os.type() do
+# {:unix, type} ->
+# "#{type}"
- _ ->
- "other"
- end
- end
+# _ ->
+# "other"
+# end
+# end
- defp os_version do
- case :os.version() do
- {major, minor, patch} ->
- "#{major}.#{minor}.#{patch}"
+# defp os_version do
+# case :os.version() do
+# {major, minor, patch} ->
+# "#{major}.#{minor}.#{patch}"
- _ ->
- "0.0.0"
- end
- end
-end
+# _ ->
+# "0.0.0"
+# end
+# end
+# end
diff --git a/apps/domain/lib/domain/telemetry/timer.ex b/apps/domain/lib/domain/telemetry/timer.ex
index 159317907..4cf2fe83e 100644
--- a/apps/domain/lib/domain/telemetry/timer.ex
+++ b/apps/domain/lib/domain/telemetry/timer.ex
@@ -1,36 +1,36 @@
-defmodule Domain.Telemetry.Timer do
- use GenServer
- alias Domain.Telemetry
+# defmodule Domain.Telemetry.Timer do
+# use GenServer
+# alias Domain.Telemetry
- @initial_delay 60 * 1_000
- @interval 43_200
+# @initial_delay 60 * 1_000
+# @interval 43_200
- def start_link(opts) do
- GenServer.start_link(__MODULE__, opts, name: __MODULE__)
- end
+# def start_link(opts) do
+# GenServer.start_link(__MODULE__, opts, name: __MODULE__)
+# end
- @impl GenServer
- def init(_opts) do
- # Send ping after 1 minute
- :timer.send_after(@initial_delay, :start_interval)
+# @impl GenServer
+# def init(_opts) do
+# # Send ping after 1 minute
+# :timer.send_after(@initial_delay, :start_interval)
- {:ok, %{}}
- end
+# {:ok, %{}}
+# end
- @impl GenServer
- def handle_info(:start_interval, state) do
- # Continue pinging twice a day
- :timer.send_interval(@interval * 1_000, :tick)
+# @impl GenServer
+# def handle_info(:start_interval, state) do
+# # Continue pinging twice a day
+# :timer.send_interval(@interval * 1_000, :tick)
- :ok = Telemetry.ping()
+# :ok = Telemetry.ping()
- {:noreply, state}
- end
+# {:noreply, state}
+# end
- @impl GenServer
- def handle_info(:tick, state) do
- :ok = Telemetry.ping()
+# @impl GenServer
+# def handle_info(:tick, state) do
+# :ok = Telemetry.ping()
- {:noreply, state}
- end
-end
+# {:noreply, state}
+# end
+# end
diff --git a/apps/domain/lib/domain/validator.ex b/apps/domain/lib/domain/validator.ex
index 07d1c17f1..bfcaf22d1 100644
--- a/apps/domain/lib/domain/validator.ex
+++ b/apps/domain/lib/domain/validator.ex
@@ -296,6 +296,16 @@ defmodule Domain.Validator do
end
end
+ def validate_datetime(changeset, field, greater_than: greater_than) do
+ validate_change(changeset, field, fn _current_field, value ->
+ if DateTime.compare(value, greater_than) == :gt do
+ []
+ else
+ [{field, "must be greater than #{inspect(greater_than)}"}]
+ end
+ end)
+ end
+
@doc """
Applies a validation function for every elements of the list.
diff --git a/apps/domain/priv/repo/migrations/20230405181921_create_clients.exs b/apps/domain/priv/repo/migrations/20230405181921_recreate_devices.exs
similarity index 72%
rename from apps/domain/priv/repo/migrations/20230405181921_create_clients.exs
rename to apps/domain/priv/repo/migrations/20230405181921_recreate_devices.exs
index bd84a7738..81e6f6182 100644
--- a/apps/domain/priv/repo/migrations/20230405181921_create_clients.exs
+++ b/apps/domain/priv/repo/migrations/20230405181921_recreate_devices.exs
@@ -1,8 +1,10 @@
-defmodule Domain.Repo.Migrations.CreateClients do
+defmodule Domain.Repo.Migrations.RecreateDevices do
use Ecto.Migration
def change do
- create table(:clients, primary_key: false) do
+ drop(table(:devices))
+
+ create table(:devices, primary_key: false) do
add(:id, :uuid, primary_key: true)
add(:external_id, :string, null: false)
@@ -40,28 +42,28 @@ defmodule Domain.Repo.Migrations.CreateClients do
timestamps(type: :utc_datetime_usec)
end
- # Used to list clients for a user
- create(index(:clients, [:user_id], where: "deleted_at IS NULL"))
+ # Used to list devices for a user
+ create(index(:devices, [:user_id], where: "deleted_at IS NULL"))
# Used for upserts
create(
- index(:clients, [:account_id, :user_id, :external_id],
+ index(:devices, [:account_id, :user_id, :external_id],
unique: true,
where: "deleted_at IS NULL"
)
)
# Used to enforce unique IPv4 and IPv6 addresses.
- create(index(:clients, [:account_id, :ipv4], unique: true, where: "deleted_at IS NULL"))
- create(index(:clients, [:account_id, :ipv6], unique: true, where: "deleted_at IS NULL"))
+ create(index(:devices, [:account_id, :ipv4], unique: true, where: "deleted_at IS NULL"))
+ create(index(:devices, [:account_id, :ipv6], unique: true, where: "deleted_at IS NULL"))
# Used to enforce unique names and public keys.
create(
- index(:clients, [:account_id, :user_id, :name], unique: true, where: "deleted_at IS NULL")
+ index(:devices, [:account_id, :user_id, :name], unique: true, where: "deleted_at IS NULL")
)
create(
- index(:clients, [:account_id, :user_id, :public_key],
+ index(:devices, [:account_id, :user_id, :public_key],
unique: true,
where: "deleted_at IS NULL"
)
diff --git a/apps/domain/priv/repo/migrations/20230425083010_remove_devices.exs b/apps/domain/priv/repo/migrations/20230425083010_remove_devices.exs
deleted file mode 100644
index 450de3afe..000000000
--- a/apps/domain/priv/repo/migrations/20230425083010_remove_devices.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule Domain.Repo.Migrations.RemoveDevices do
- use Ecto.Migration
-
- def change do
- drop(table(:devices))
- end
-end
diff --git a/apps/domain/priv/repo/migrations/20230426181111_remove_users.exs b/apps/domain/priv/repo/migrations/20230426181111_remove_users.exs
index 5c830ebb2..1930c6a35 100644
--- a/apps/domain/priv/repo/migrations/20230426181111_remove_users.exs
+++ b/apps/domain/priv/repo/migrations/20230426181111_remove_users.exs
@@ -11,30 +11,30 @@ defmodule Domain.Repo.Migrations.RemoveUsers do
create(index(:api_tokens, [:actor_id]))
- ## Clients
+ ## Devices
- alter table(:clients) do
+ alter table(:devices) do
remove(:user_id, references(:users, type: :binary_id), null: false)
add(:actor_id, references(:actors, type: :binary_id), null: false)
add(:identity_id, references(:auth_identities, type: :binary_id), null: false)
end
create(
- index(:clients, [:account_id, :actor_id, :external_id],
+ index(:devices, [:account_id, :actor_id, :external_id],
unique: true,
where: "deleted_at IS NULL"
)
)
create(
- index(:clients, [:account_id, :actor_id, :name],
+ index(:devices, [:account_id, :actor_id, :name],
unique: true,
where: "deleted_at IS NULL"
)
)
create(
- index(:clients, [:account_id, :actor_id, :public_key],
+ index(:devices, [:account_id, :actor_id, :public_key],
unique: true,
where: "deleted_at IS NULL"
)
diff --git a/apps/domain/priv/repo/migrations/20230519192732_drop_api_tokens.exs b/apps/domain/priv/repo/migrations/20230519192732_drop_api_tokens.exs
new file mode 100644
index 000000000..95ffe105a
--- /dev/null
+++ b/apps/domain/priv/repo/migrations/20230519192732_drop_api_tokens.exs
@@ -0,0 +1,7 @@
+defmodule Domain.Repo.Migrations.DropApiTokens do
+ use Ecto.Migration
+
+ def change do
+ drop(table(:api_tokens))
+ end
+end
diff --git a/apps/domain/priv/repo/migrations/20230523012008_remove_actors_role.exs b/apps/domain/priv/repo/migrations/20230523012008_remove_actors_role.exs
new file mode 100644
index 000000000..d243a2c12
--- /dev/null
+++ b/apps/domain/priv/repo/migrations/20230523012008_remove_actors_role.exs
@@ -0,0 +1,9 @@
+defmodule Domain.Repo.Migrations.RemoveActorsRole do
+ use Ecto.Migration
+
+ def change do
+ alter table(:actors) do
+ remove(:role)
+ end
+ end
+end
diff --git a/apps/domain/priv/repo/migrations/20230523181533_cleanup_configurations.exs b/apps/domain/priv/repo/migrations/20230523181533_cleanup_configurations.exs
new file mode 100644
index 000000000..f13fcde3b
--- /dev/null
+++ b/apps/domain/priv/repo/migrations/20230523181533_cleanup_configurations.exs
@@ -0,0 +1,26 @@
+defmodule Domain.Repo.Migrations.CleanupConfigurations do
+ use Ecto.Migration
+
+ def change do
+ execute("delete from configurations")
+
+ alter table(:configurations) do
+ remove(:allow_unprivileged_device_management)
+ remove(:allow_unprivileged_device_configuration)
+ remove(:local_auth_enabled)
+ remove(:disable_vpn_on_oidc_error)
+ remove(:default_client_persistent_keepalive)
+ remove(:default_client_mtu)
+ remove(:default_client_endpoint)
+ remove(:default_client_dns)
+ remove(:default_client_allowed_ips)
+ remove(:vpn_session_duration)
+
+ add(:devices_upstream_dns, {:array, :string}, default: [])
+
+ add(:account_id, references(:accounts, type: :binary_id), null: false)
+ end
+
+ create(index(:configurations, [:account_id], unique: true))
+ end
+end
diff --git a/apps/domain/priv/repo/seeds.exs b/apps/domain/priv/repo/seeds.exs
index 4568c730f..3957f3148 100644
--- a/apps/domain/priv/repo/seeds.exs
+++ b/apps/domain/priv/repo/seeds.exs
@@ -35,14 +35,12 @@ admin_actor_email = "firezone@localhost"
{:ok, unprivileged_actor} =
Actors.create_actor(email_provider, unprivileged_actor_email, %{
- type: :user,
- role: :unprivileged
+ type: :account_user
})
{:ok, admin_actor} =
Actors.create_actor(email_provider, admin_actor_email, %{
- type: :user,
- role: :admin
+ type: :account_admin_user
})
{:ok, _unprivileged_actor_userpass_identity} =
@@ -70,11 +68,12 @@ admin_subject =
IO.puts("Created users: ")
-for {role, login, password, email_token} <- [
- {:unprivileged, unprivileged_actor_email, "Firezone1234", unprivileged_actor_token},
- {:admin, admin_actor_email, "Firezone1234", admin_actor_token}
+for {type, login, password, email_token} <- [
+ {unprivileged_actor.type, unprivileged_actor_email, "Firezone1234",
+ unprivileged_actor_token},
+ {admin_actor.type, admin_actor_email, "Firezone1234", admin_actor_token}
] do
- IO.puts(" #{login}, #{role}, password: #{password}, email token: #{email_token}")
+ IO.puts(" #{login}, #{type}, password: #{password}, email token: #{email_token}")
end
IO.puts("")
diff --git a/apps/domain/test/domain/actors_test.exs b/apps/domain/test/domain/actors_test.exs
index e3a104413..0d0a64e67 100644
--- a/apps/domain/test/domain/actors_test.exs
+++ b/apps/domain/test/domain/actors_test.exs
@@ -5,10 +5,10 @@ defmodule Domain.ActorsTest do
alias Domain.Actors
alias Domain.{AccountsFixtures, AuthFixtures, ActorsFixtures}
- describe "fetch_count_by_role/0" do
+ describe "fetch_count_by_type/0" do
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -19,37 +19,37 @@ defmodule Domain.ActorsTest do
}
end
- test "returns correct count of not deleted actors by role", %{
+ test "returns correct count of not deleted actors by type", %{
account: account,
subject: subject
} do
- assert fetch_count_by_role(:admin, subject) == 1
- assert fetch_count_by_role(:unprivileged, subject) == 0
+ assert fetch_count_by_type(:account_admin_user, subject) == 1
+ assert fetch_count_by_type(:account_user, subject) == 0
- ActorsFixtures.create_actor(role: :admin)
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ ActorsFixtures.create_actor(type: :account_admin_user)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
assert {:ok, _actor} = delete_actor(actor, subject)
- assert fetch_count_by_role(:admin, subject) == 1
- assert fetch_count_by_role(:unprivileged, subject) == 0
+ assert fetch_count_by_type(:account_admin_user, subject) == 1
+ assert fetch_count_by_type(:account_user, subject) == 0
- ActorsFixtures.create_actor(role: :admin, account: account)
- assert fetch_count_by_role(:admin, subject) == 2
- assert fetch_count_by_role(:unprivileged, subject) == 0
+ ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ assert fetch_count_by_type(:account_admin_user, subject) == 2
+ assert fetch_count_by_type(:account_user, subject) == 0
- ActorsFixtures.create_actor(role: :unprivileged)
- ActorsFixtures.create_actor(role: :unprivileged, account: account)
- assert fetch_count_by_role(:admin, subject) == 2
- assert fetch_count_by_role(:unprivileged, subject) == 1
+ ActorsFixtures.create_actor(type: :account_user)
+ ActorsFixtures.create_actor(type: :account_user, account: account)
+ assert fetch_count_by_type(:account_admin_user, subject) == 2
+ assert fetch_count_by_type(:account_user, subject) == 1
- for _ <- 1..5, do: ActorsFixtures.create_actor(role: :unprivileged, account: account)
- assert fetch_count_by_role(:admin, subject) == 2
- assert fetch_count_by_role(:unprivileged, subject) == 6
+ for _ <- 1..5, do: ActorsFixtures.create_actor(type: :account_user, account: account)
+ assert fetch_count_by_type(:account_admin_user, subject) == 2
+ assert fetch_count_by_type(:account_user, subject) == 6
end
test "returns error when subject can not view actors", %{subject: subject} do
subject = AuthFixtures.remove_permissions(subject)
- assert fetch_count_by_role(:foo, subject) ==
+ assert fetch_count_by_type(:foo, subject) ==
{:error,
{:unauthorized,
[missing_permissions: [Actors.Authorizer.manage_actors_permission()]]}}
@@ -69,7 +69,7 @@ defmodule Domain.ActorsTest do
test "returns own actor" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -79,7 +79,7 @@ defmodule Domain.ActorsTest do
test "returns non own actor" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -91,7 +91,7 @@ defmodule Domain.ActorsTest do
test "returns error when actor is in another account" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -121,7 +121,7 @@ defmodule Domain.ActorsTest do
end
test "returns actor" do
- actor = ActorsFixtures.create_actor(role: :admin)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user)
assert {:ok, returned_actor} = fetch_actor_by_id(actor.id)
assert returned_actor.id == actor.id
end
@@ -141,7 +141,7 @@ defmodule Domain.ActorsTest do
end
test "returns actor" do
- actor = ActorsFixtures.create_actor(role: :admin)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user)
assert returned_actor = fetch_actor_by_id!(actor.id)
assert returned_actor.id == actor.id
end
@@ -166,11 +166,11 @@ defmodule Domain.ActorsTest do
assert list_actors(subject, hydrate: []) == {:ok, []}
end
- test "returns list of actors in all roles" do
+ test "returns list of actors in all types" do
account = AccountsFixtures.create_account()
- actor1 = ActorsFixtures.create_actor(account: account, role: :admin)
- actor2 = ActorsFixtures.create_actor(account: account, role: :unprivileged)
- ActorsFixtures.create_actor(role: :unprivileged)
+ actor1 = ActorsFixtures.create_actor(account: account, type: :account_admin_user)
+ actor2 = ActorsFixtures.create_actor(account: account, type: :account_user)
+ ActorsFixtures.create_actor(type: :account_user)
identity1 = AuthFixtures.create_identity(account: account, actor: actor1)
subject = AuthFixtures.create_subject(identity1)
@@ -212,7 +212,6 @@ defmodule Domain.ActorsTest do
refute changeset.valid?
assert errors_on(changeset) == %{
- role: ["can't be blank"],
type: ["can't be blank"]
}
end
@@ -221,13 +220,12 @@ defmodule Domain.ActorsTest do
provider: provider,
provider_identifier: provider_identifier
} do
- attrs = ActorsFixtures.actor_attrs(role: :foo, type: :bar)
+ attrs = ActorsFixtures.actor_attrs(type: :foo)
assert {:error, changeset} = create_actor(provider, provider_identifier, attrs)
refute changeset.valid?
assert errors_on(changeset) == %{
- role: ["is invalid"],
type: ["is invalid"]
}
end
@@ -242,21 +240,10 @@ defmodule Domain.ActorsTest do
assert errors_on(changeset) == %{provider_identifier: ["has already been taken"]}
end
- test "creates an actor in given role", %{
- provider: provider
- } do
- for role <- [:admin, :unprivileged] do
- attrs = ActorsFixtures.actor_attrs(role: role)
- provider_identifier = AuthFixtures.random_provider_identifier(provider)
- assert {:ok, actor} = create_actor(provider, provider_identifier, attrs)
- assert actor.role == role
- end
- end
-
test "creates an actor in given type", %{
provider: provider
} do
- for type <- [:user, :service_account] do
+ for type <- [:account_user, :account_admin_user, :service_account] do
attrs = ActorsFixtures.actor_attrs(type: type)
provider_identifier = AuthFixtures.random_provider_identifier(provider)
assert {:ok, actor} = create_actor(provider, provider_identifier, attrs)
@@ -273,7 +260,7 @@ defmodule Domain.ActorsTest do
assert {:ok, actor} = create_actor(provider, provider_identifier, attrs)
assert actor.type == attrs.type
- assert actor.role == attrs.role
+ assert actor.type == attrs.type
assert is_nil(actor.disabled_at)
assert is_nil(actor.deleted_at)
@@ -310,7 +297,7 @@ defmodule Domain.ActorsTest do
provider: provider,
provider_identifier: provider_identifier
} do
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
subject =
AuthFixtures.create_identity(account: account, actor: actor)
@@ -336,7 +323,7 @@ defmodule Domain.ActorsTest do
provider: provider,
provider_identifier: provider_identifier
} do
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
subject =
AuthFixtures.create_identity(account: account, actor: actor)
@@ -354,22 +341,22 @@ defmodule Domain.ActorsTest do
MapSet.difference(admin_permissions, MapSet.new(required_permissions))
|> MapSet.to_list()
- attrs = %{type: :user, role: :admin}
+ attrs = %{type: :account_admin_user}
assert create_actor(provider, provider_identifier, attrs, subject) ==
{:error, {:unauthorized, privilege_escalation: missing_permissions}}
- attrs = %{"type" => "user", "role" => "admin"}
+ attrs = %{"type" => "account_admin_user"}
assert create_actor(provider, provider_identifier, attrs, subject) ==
{:error, {:unauthorized, privilege_escalation: missing_permissions}}
end
end
- describe "change_actor_role/3" do
+ describe "change_actor_type/3" do
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -380,25 +367,29 @@ defmodule Domain.ActorsTest do
}
end
- test "allows admin to change other actors role", %{account: account, subject: subject} do
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- assert {:ok, %{role: :unprivileged}} = change_actor_role(actor, :unprivileged, subject)
- assert {:ok, %{role: :admin}} = change_actor_role(actor, :admin, subject)
+ test "allows admin to change other actors type", %{account: account, subject: subject} do
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ assert {:ok, %{type: :account_user}} = change_actor_type(actor, :account_user, subject)
- actor = ActorsFixtures.create_actor(role: :unprivileged, account: account)
- assert {:ok, %{role: :unprivileged}} = change_actor_role(actor, :unprivileged, subject)
- assert {:ok, %{role: :admin}} = change_actor_role(actor, :admin, subject)
+ assert {:ok, %{type: :account_admin_user}} =
+ change_actor_type(actor, :account_admin_user, subject)
+
+ actor = ActorsFixtures.create_actor(type: :account_user, account: account)
+ assert {:ok, %{type: :account_user}} = change_actor_type(actor, :account_user, subject)
+
+ assert {:ok, %{type: :account_admin_user}} =
+ change_actor_type(actor, :account_admin_user, subject)
end
- test "returns error when subject can not manage roles", %{account: account} do
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ test "returns error when subject can not manage types", %{account: account} do
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
subject =
AuthFixtures.create_identity(account: account, actor: actor)
|> AuthFixtures.create_subject()
|> AuthFixtures.remove_permissions()
- assert change_actor_role(actor, :foo, subject) ==
+ assert change_actor_type(actor, :foo, subject) ==
{:error,
{:unauthorized,
[missing_permissions: [Actors.Authorizer.manage_actors_permission()]]}}
@@ -408,8 +399,8 @@ defmodule Domain.ActorsTest do
describe "disable_actor/2" do
test "disables a given actor" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -425,7 +416,7 @@ defmodule Domain.ActorsTest do
test "returns error when trying to disable the last admin actor" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(account: account, role: :admin)
+ actor = ActorsFixtures.create_actor(account: account, type: :account_admin_user)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -434,8 +425,8 @@ defmodule Domain.ActorsTest do
test "last admin check ignores admins in other accounts" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- ActorsFixtures.create_actor(role: :admin)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ ActorsFixtures.create_actor(type: :account_admin_user)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -444,8 +435,8 @@ defmodule Domain.ActorsTest do
test "last admin check ignores disabled admins" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, _other_actor} = disable_actor(other_actor, subject)
@@ -462,8 +453,8 @@ defmodule Domain.ActorsTest do
account = AccountsFixtures.create_account()
- actor_one = ActorsFixtures.create_actor(role: :admin, account: account)
- actor_two = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor_one = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ actor_two = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
subject_one = AuthFixtures.create_subject(actor_one)
subject_two = AuthFixtures.create_subject(actor_two)
@@ -480,8 +471,8 @@ defmodule Domain.ActorsTest do
test "does not do anything when an actor is disabled twice" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -492,8 +483,8 @@ defmodule Domain.ActorsTest do
test "does not allow to disable actors in other accounts" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -502,7 +493,7 @@ defmodule Domain.ActorsTest do
test "returns error when subject can not disable actors" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
subject =
AuthFixtures.create_identity(account: account, actor: actor)
@@ -519,8 +510,8 @@ defmodule Domain.ActorsTest do
describe "enable_actor/2" do
test "enables a given actor" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -538,8 +529,8 @@ defmodule Domain.ActorsTest do
test "does not do anything when an actor is already enabled" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -552,8 +543,8 @@ defmodule Domain.ActorsTest do
test "does not allow to enable actors in other accounts" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -562,7 +553,7 @@ defmodule Domain.ActorsTest do
test "returns error when subject can not enable actors" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
subject =
AuthFixtures.create_identity(account: account, actor: actor)
@@ -579,8 +570,8 @@ defmodule Domain.ActorsTest do
describe "delete_actor/2" do
test "deletes a given actor" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -596,7 +587,7 @@ defmodule Domain.ActorsTest do
test "returns error when trying to delete the last admin actor" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -605,8 +596,8 @@ defmodule Domain.ActorsTest do
test "last admin check ignores admins in other accounts" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- ActorsFixtures.create_actor(role: :admin)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ ActorsFixtures.create_actor(type: :account_admin_user)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -615,8 +606,8 @@ defmodule Domain.ActorsTest do
test "last admin check ignores disabled admins" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, _other_actor} = disable_actor(other_actor, subject)
@@ -633,8 +624,8 @@ defmodule Domain.ActorsTest do
account = AccountsFixtures.create_account()
- actor_one = ActorsFixtures.create_actor(role: :admin, account: account)
- actor_two = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor_one = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ actor_two = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
subject_one = AuthFixtures.create_subject(actor_one)
subject_two = AuthFixtures.create_subject(actor_two)
@@ -651,8 +642,8 @@ defmodule Domain.ActorsTest do
test "does not allow to delete an actor twice" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -662,8 +653,8 @@ defmodule Domain.ActorsTest do
test "does not allow to delete actors in other accounts" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- other_actor = ActorsFixtures.create_actor(role: :admin)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ other_actor = ActorsFixtures.create_actor(type: :account_admin_user)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -672,7 +663,7 @@ defmodule Domain.ActorsTest do
test "returns error when subject can not delete actors" do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
subject =
AuthFixtures.create_identity(account: account, actor: actor)
diff --git a/apps/domain/test/domain/api_tokens_test.exs b/apps/domain/test/domain/api_tokens_test.exs
deleted file mode 100644
index 6dd0698fc..000000000
--- a/apps/domain/test/domain/api_tokens_test.exs
+++ /dev/null
@@ -1,439 +0,0 @@
-# defmodule Domain.ApiTokensTest do
-# use Domain.DataCase, async: true
-# import Domain.ApiTokens
-# alias Domain.ApiTokens.{ApiToken, Authorizer}
-# alias Domain.{AccountsFixtures, AuthFixtures, ActorsFixtures, ApiTokensFixtures}
-
-# setup do
-# account = AccountsFixtures.create_account()
-# actor = ActorsFixtures.create_actor(role: :admin, account: account)
-# identity = AuthFixtures.create_identity(account: account, actor: actor)
-# subject = AuthFixtures.create_subject(identity)
-
-# %{actor: actor, subject: subject}
-# end
-
-# describe "count_by_actor_id/1" do
-# test "returns 0 when no actor exist" do
-# assert count_by_actor_id(Ecto.UUID.generate()) == 0
-# end
-
-# test "returns the number of api_tokens for a actor" do
-# actor = ActorsFixtures.create_actor(role: :admin)
-# assert count_by_actor_id(actor.id) == 0
-
-# ApiTokensFixtures.create_api_token(actor: actor)
-# assert count_by_actor_id(actor.id) == 1
-
-# ApiTokensFixtures.create_api_token(actor: actor)
-# assert count_by_actor_id(actor.id) == 2
-# end
-# end
-
-# describe "list_api_tokens/1" do
-# test "returns empty list when there are no api tokens", %{subject: subject} do
-# assert list_api_tokens(subject) == {:ok, []}
-# end
-
-# test "does not return api tokens when actor has no access to them", %{subject: subject} do
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-
-# ApiTokensFixtures.create_api_token()
-# assert list_api_tokens(subject) == {:ok, []}
-# end
-
-# test "returns other actor api tokens when subject has manage permission", %{subject: subject} do
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-# |> AuthFixtures.add_permission(Authorizer.manage_api_tokens_permission())
-
-# api_token = ApiTokensFixtures.create_api_token()
-
-# assert list_api_tokens(subject) == {:ok, [api_token]}
-# end
-
-# test "returns all api tokens for a actor", %{actor: actor, subject: subject} do
-# api_token = ApiTokensFixtures.create_api_token(actor: actor)
-# assert list_api_tokens(subject) == {:ok, [api_token]}
-
-# ApiTokensFixtures.create_api_token(actor: actor)
-# assert {:ok, api_tokens} = list_api_tokens(subject)
-# assert length(api_tokens) == 2
-# end
-
-# test "returns error when subject has no permission to view api tokens", %{subject: subject} do
-# subject = AuthFixtures.remove_permissions(subject)
-
-# assert list_api_tokens(subject) ==
-# {:error,
-# {:unauthorized,
-# [
-# missing_permissions: [
-# {:one_of,
-# [
-# Authorizer.manage_api_tokens_permission(),
-# Authorizer.manage_own_api_tokens_permission()
-# ]}
-# ]
-# ]}}
-# end
-# end
-
-# describe "list_api_tokens_by_actor_id/2" do
-# test "returns api token that belongs to another actor with manage permission", %{
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token()
-
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-# |> AuthFixtures.add_permission(Authorizer.manage_api_tokens_permission())
-
-# assert list_api_tokens_by_actor_id(api_token.actor_id, subject) ==
-# {:ok, [api_token]}
-# end
-
-# test "does not return api token that belongs to another actor with manage_own permission", %{
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token()
-
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-
-# assert list_api_tokens_by_actor_id(api_token.actor_id, subject) == {:ok, []}
-# end
-
-# test "returns api tokens scoped to a actor", %{actor: actor, subject: subject} do
-# ApiTokensFixtures.create_api_token(actor: actor)
-# ApiTokensFixtures.create_api_token(actor: actor)
-
-# assert {:ok, api_tokens} = list_api_tokens_by_actor_id(actor.id, subject)
-# assert length(api_tokens) == 2
-# end
-
-# test "returns error when api token does not exist", %{subject: subject} do
-# assert list_api_tokens_by_actor_id(Ecto.UUID.generate(), subject) == {:ok, []}
-# end
-
-# test "returns error when actor ID is not a valid UUID", %{subject: subject} do
-# assert list_api_tokens_by_actor_id("foo", subject) == {:ok, []}
-# end
-
-# test "returns error when subject has no permission to view api tokens", %{subject: subject} do
-# subject = AuthFixtures.remove_permissions(subject)
-
-# assert list_api_tokens_by_actor_id(Ecto.UUID.generate(), subject) ==
-# {:error,
-# {:unauthorized,
-# [
-# missing_permissions: [
-# {:one_of,
-# [
-# Authorizer.manage_api_tokens_permission(),
-# Authorizer.manage_own_api_tokens_permission()
-# ]}
-# ]
-# ]}}
-# end
-# end
-
-# describe "fetch_api_token_by_id/2" do
-# test "returns error when UUID is invalid", %{subject: subject} do
-# assert fetch_api_token_by_id("foo", subject) == {:error, :not_found}
-# end
-
-# test "returns api token by id", %{actor: actor, subject: subject} do
-# api_token = ApiTokensFixtures.create_api_token(actor: actor)
-# assert fetch_api_token_by_id(api_token.id, subject) == {:ok, api_token}
-# end
-
-# test "returns api token that belongs to another actor with manage permission", %{
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token()
-
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-# |> AuthFixtures.add_permission(Authorizer.manage_api_tokens_permission())
-
-# assert fetch_api_token_by_id(api_token.id, subject) == {:ok, api_token}
-# end
-
-# test "does not return api token that belongs to another actor with manage_own permission", %{
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token()
-
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-
-# assert fetch_api_token_by_id(api_token.id, subject) == {:error, :not_found}
-# end
-
-# test "returns error when api token does not exist", %{subject: subject} do
-# assert fetch_api_token_by_id(Ecto.UUID.generate(), subject) ==
-# {:error, :not_found}
-# end
-
-# test "returns error when subject has no permission to view api tokens", %{subject: subject} do
-# subject = AuthFixtures.remove_permissions(subject)
-
-# assert fetch_api_token_by_id(Ecto.UUID.generate(), subject) ==
-# {:error,
-# {:unauthorized,
-# [
-# missing_permissions: [
-# {:one_of,
-# [
-# Authorizer.manage_api_tokens_permission(),
-# Authorizer.manage_own_api_tokens_permission()
-# ]}
-# ]
-# ]}}
-# end
-# end
-
-# describe "fetch_unexpired_api_token_by_id/2" do
-# test "returns error when UUID is invalid", %{subject: subject} do
-# assert fetch_unexpired_api_token_by_id("foo", subject) == {:error, :not_found}
-# end
-
-# test "returns api token by id", %{actor: actor, subject: subject} do
-# api_token = ApiTokensFixtures.create_api_token(actor: actor)
-# assert fetch_unexpired_api_token_by_id(api_token.id, subject) == {:ok, api_token}
-# end
-
-# test "returns error for expired token", %{actor: actor, subject: subject} do
-# api_token =
-# ApiTokensFixtures.create_api_token(actor: actor, expires_in: 1)
-# |> ApiTokensFixtures.expire_api_token()
-
-# assert fetch_unexpired_api_token_by_id(api_token.id, subject) ==
-# {:error, :not_found}
-# end
-
-# test "returns api token that belongs to another actor with manage permission", %{
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token()
-
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-# |> AuthFixtures.add_permission(Authorizer.manage_api_tokens_permission())
-
-# assert fetch_unexpired_api_token_by_id(api_token.id, subject) == {:ok, api_token}
-# end
-
-# test "does not return api token that belongs to another actor with manage_own permission", %{
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token()
-
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-
-# assert fetch_unexpired_api_token_by_id(api_token.id, subject) ==
-# {:error, :not_found}
-# end
-
-# test "returns error when api token does not exist", %{subject: subject} do
-# assert fetch_unexpired_api_token_by_id(Ecto.UUID.generate(), subject) ==
-# {:error, :not_found}
-# end
-
-# test "returns error when subject has no permission to view api tokens", %{subject: subject} do
-# subject = AuthFixtures.remove_permissions(subject)
-
-# assert fetch_unexpired_api_token_by_id(Ecto.UUID.generate(), subject) ==
-# {:error,
-# {:unauthorized,
-# [
-# missing_permissions: [
-# {:one_of,
-# [
-# Authorizer.manage_api_tokens_permission(),
-# Authorizer.manage_own_api_tokens_permission()
-# ]}
-# ]
-# ]}}
-# end
-# end
-
-# describe "fetch_unexpired_api_token_by_id/1" do
-# test "fetches the unexpired token" do
-# api_token = ApiTokensFixtures.create_api_token()
-# assert fetch_unexpired_api_token_by_id(api_token.id) == {:ok, api_token}
-# end
-
-# test "returns error for expired token" do
-# api_token =
-# ApiTokensFixtures.create_api_token(%{"expires_in" => 1})
-# |> ApiTokensFixtures.expire_api_token()
-
-# assert fetch_unexpired_api_token_by_id(api_token.id) == {:error, :not_found}
-# end
-# end
-
-# describe "new_api_token/1" do
-# test "returns api token changeset" do
-# assert %Ecto.Changeset{data: %ApiToken{}, changes: changes} = new_api_token()
-# assert Map.has_key?(changes, :expires_at)
-# end
-# end
-
-# describe "create_api_token/2" do
-# test "creates an api_token", %{actor: actor, subject: subject} do
-# attrs = %{
-# "expires_in" => 1
-# }
-
-# assert {:ok, %ApiToken{} = api_token} = create_api_token(attrs, subject)
-
-# # Within 10 seconds
-# assert_in_delta DateTime.to_unix(api_token.expires_at),
-# DateTime.to_unix(DateTime.add(DateTime.utc_now(), 1, :day)),
-# 10
-
-# assert api_token.actor_id == actor.id
-# assert api_token.expires_in == 1
-# end
-
-# test "returns changeset error on invalid data", %{subject: subject} do
-# attrs = %{
-# "expires_in" => 0
-# }
-
-# assert {:error, %Ecto.Changeset{} = changeset} = create_api_token(attrs, subject)
-
-# assert changeset.valid? == false
-# assert errors_on(changeset) == %{expires_in: ["must be greater than or equal to 1"]}
-# end
-
-# test "returns error when subject has no permission to create api tokens", %{subject: subject} do
-# attrs = %{
-# "expires_in" => 0
-# }
-
-# subject = AuthFixtures.remove_permissions(subject)
-
-# assert create_api_token(attrs, subject) ==
-# {:error,
-# {:unauthorized,
-# [missing_permissions: [Authorizer.manage_own_api_tokens_permission()]]}}
-# end
-# end
-
-# describe "api_token_expired?/1" do
-# test "returns true when expired" do
-# api_token =
-# ApiTokensFixtures.create_api_token(%{"expires_in" => 1})
-# |> ApiTokensFixtures.expire_api_token()
-
-# assert api_token_expired?(api_token) == true
-# end
-
-# test "returns false when not expired" do
-# api_token = ApiTokensFixtures.create_api_token(%{"expires_in" => 1})
-# assert api_token_expired?(api_token) == false
-# end
-# end
-
-# describe "delete_api_token_by_id/1" do
-# test "deletes the api token that belongs to a subject actor", %{
-# actor: actor,
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token(actor: actor)
-
-# assert {:ok, deleted_api_token} = delete_api_token_by_id(api_token.id, subject)
-
-# assert deleted_api_token.id == api_token.id
-# refute Repo.one(ApiToken)
-# end
-
-# test "deletes api token that belongs to another actor with manage permission", %{
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token()
-
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-# |> AuthFixtures.add_permission(Authorizer.manage_api_tokens_permission())
-
-# assert {:ok, deleted_api_token} = delete_api_token_by_id(api_token.id, subject)
-
-# assert deleted_api_token.id == api_token.id
-# refute Repo.one(ApiToken)
-# end
-
-# test "does not delete api token that belongs to another actor with manage_own permission",
-# %{
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token()
-
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-
-# assert delete_api_token_by_id(api_token.id, subject) ==
-# {:error, :not_found}
-# end
-
-# test "does not delete api token that belongs to another actor with just view permission", %{
-# subject: subject
-# } do
-# api_token = ApiTokensFixtures.create_api_token()
-
-# subject =
-# subject
-# |> AuthFixtures.remove_permissions()
-# |> AuthFixtures.add_permission(Authorizer.manage_own_api_tokens_permission())
-
-# assert delete_api_token_by_id(api_token.id, subject) ==
-# {:error, :not_found}
-# end
-
-# test "returns error when api token does not exist", %{subject: subject} do
-# assert delete_api_token_by_id(Ecto.UUID.generate(), subject) == {:error, :not_found}
-# end
-
-# test "returns error when subject can not view api tokens", %{subject: subject} do
-# subject = AuthFixtures.remove_permissions(subject)
-
-# assert delete_api_token_by_id(Ecto.UUID.generate(), subject) ==
-# {:error,
-# {:unauthorized,
-# [
-# missing_permissions: [
-# {:one_of,
-# [
-# Authorizer.manage_api_tokens_permission(),
-# Authorizer.manage_own_api_tokens_permission()
-# ]}
-# ]
-# ]}}
-# end
-# end
-# end
diff --git a/apps/domain/test/domain/auth/adapters/email_test.exs b/apps/domain/test/domain/auth/adapters/email_test.exs
index 60e6d8541..0da90e0ef 100644
--- a/apps/domain/test/domain/auth/adapters/email_test.exs
+++ b/apps/domain/test/domain/auth/adapters/email_test.exs
@@ -22,8 +22,8 @@ defmodule Domain.Auth.Adapters.EmailTest do
assert %{
provider_state: %{
- sign_in_token_created_at: %DateTime{},
- sign_in_token_hash: sign_in_token_hash
+ "sign_in_token_created_at" => %DateTime{},
+ "sign_in_token_hash" => sign_in_token_hash
},
provider_virtual_state: %{sign_in_token: sign_in_token}
} = changeset.changes
@@ -59,8 +59,8 @@ defmodule Domain.Auth.Adapters.EmailTest do
assert {:ok, identity} = request_sign_in_token(identity)
assert %{
- sign_in_token_created_at: sign_in_token_created_at,
- sign_in_token_hash: sign_in_token_hash
+ "sign_in_token_created_at" => sign_in_token_created_at,
+ "sign_in_token_hash" => sign_in_token_hash
} = identity.provider_state
assert %{
diff --git a/apps/domain/test/domain/auth/adapters/openid_connect_test.exs b/apps/domain/test/domain/auth/adapters/openid_connect_test.exs
index 3d498d15d..cc8801a75 100644
--- a/apps/domain/test/domain/auth/adapters/openid_connect_test.exs
+++ b/apps/domain/test/domain/auth/adapters/openid_connect_test.exs
@@ -3,14 +3,14 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
import Domain.Auth.Adapters.OpenIDConnect
alias Domain.Auth
alias Domain.Auth.Adapters.OpenIDConnect.{PKCE, State}
- alias Domain.{AccountsFixtures, AuthFixtures, ConfigFixtures}
+ alias Domain.{AccountsFixtures, AuthFixtures}
describe "identity_changeset/2" do
setup do
account = AccountsFixtures.create_account()
{provider, bypass} =
- ConfigFixtures.start_openid_providers(["google"])
+ AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
changeset = %Auth.Identity{} |> Ecto.Changeset.change()
@@ -56,7 +56,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
test "returns changeset on valid adapter config" do
account = AccountsFixtures.create_account()
- {_bypass, discovery_document_uri} = ConfigFixtures.discovery_document_server()
+ {_bypass, discovery_document_uri} = AuthFixtures.discovery_document_server()
attrs =
AuthFixtures.provider_attrs(
@@ -77,11 +77,11 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
assert provider.adapter == attrs.adapter
assert provider.adapter_config == %{
- scope: "openid email profile",
- response_type: "code",
- client_id: "client_id",
- client_secret: "client_secret",
- discovery_document_uri: discovery_document_uri
+ "scope" => "openid email profile",
+ "response_type" => "code",
+ "client_id" => "client_id",
+ "client_secret" => "client_secret",
+ "discovery_document_uri" => discovery_document_uri
}
end
end
@@ -98,7 +98,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
account = AccountsFixtures.create_account()
{provider, bypass} =
- ConfigFixtures.start_openid_providers(["google"])
+ AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
assert {:ok, authorization_uri, {state, verifier}} =
@@ -138,7 +138,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
account = AccountsFixtures.create_account()
{provider, bypass} =
- ConfigFixtures.start_openid_providers(["google"])
+ AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider)
@@ -153,8 +153,8 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
} do
{token, claims} = generate_token(provider, identity)
- ConfigFixtures.expect_refresh_token(bypass, %{"id_token" => token})
- ConfigFixtures.expect_userinfo(bypass)
+ AuthFixtures.expect_refresh_token(bypass, %{"id_token" => token})
+ AuthFixtures.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -189,7 +189,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
} do
{token, _claims} = generate_token(provider, identity)
- ConfigFixtures.expect_refresh_token(bypass, %{
+ AuthFixtures.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
@@ -197,7 +197,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
"expires_in" => 3600
})
- ConfigFixtures.expect_userinfo(bypass)
+ AuthFixtures.expect_userinfo(bypass)
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -220,7 +220,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
{token, _claims} = generate_token(provider, identity, %{"exp" => forty_seconds_ago})
- ConfigFixtures.expect_refresh_token(bypass, %{"id_token" => token})
+ AuthFixtures.expect_refresh_token(bypass, %{"id_token" => token})
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -235,7 +235,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
} do
token = "foo"
- ConfigFixtures.expect_refresh_token(bypass, %{"id_token" => token})
+ AuthFixtures.expect_refresh_token(bypass, %{"id_token" => token})
code_verifier = PKCE.code_verifier()
redirect_uri = "https://example.com/"
@@ -263,7 +263,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
account = AccountsFixtures.create_account()
{provider, bypass} =
- ConfigFixtures.start_openid_providers(["google"])
+ AuthFixtures.start_openid_providers(["google"])
|> AuthFixtures.create_openid_connect_provider(account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider)
@@ -278,7 +278,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
} do
{token, claims} = generate_token(provider, identity)
- ConfigFixtures.expect_refresh_token(bypass, %{
+ AuthFixtures.expect_refresh_token(bypass, %{
"token_type" => "Bearer",
"id_token" => token,
"access_token" => "MY_ACCESS_TOKEN",
@@ -286,7 +286,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
"expires_in" => nil
})
- ConfigFixtures.expect_userinfo(bypass)
+ AuthFixtures.expect_userinfo(bypass)
assert {:ok, identity, expires_at} = refresh_token(identity)
@@ -314,7 +314,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
end
defp generate_token(provider, identity, claims \\ %{}) do
- jwk = ConfigFixtures.jwks_attrs()
+ jwk = AuthFixtures.jwks_attrs()
claims =
Map.merge(
diff --git a/apps/domain/test/domain/auth/adapters/token_test.exs b/apps/domain/test/domain/auth/adapters/token_test.exs
new file mode 100644
index 000000000..e016ddc0a
--- /dev/null
+++ b/apps/domain/test/domain/auth/adapters/token_test.exs
@@ -0,0 +1,137 @@
+defmodule Domain.Auth.Adapters.TokenTest do
+ use Domain.DataCase, async: true
+ import Domain.Auth.Adapters.Token
+ alias Domain.Auth
+ alias Domain.{AccountsFixtures, AuthFixtures}
+
+ describe "identity_changeset/2" do
+ setup do
+ account = AccountsFixtures.create_account()
+ provider = AuthFixtures.create_token_provider(account: account)
+
+ %{
+ account: account,
+ provider: provider
+ }
+ end
+
+ test "puts secret hash in the provider state", %{provider: provider} do
+ changeset =
+ %Auth.Identity{}
+ |> Ecto.Changeset.change(
+ provider_virtual_state: %{
+ expires_at: DateTime.utc_now() |> DateTime.add(1, :day)
+ }
+ )
+
+ assert %Ecto.Changeset{} = changeset = identity_changeset(provider, changeset)
+ assert %{provider_state: state, provider_virtual_state: virtual_state} = changeset.changes
+
+ assert %{"secret_hash" => secret_hash} = state
+ assert %{secret: secret} = virtual_state
+ assert Domain.Crypto.equal?(secret, secret_hash)
+ end
+
+ test "returns error on invalid attrs", %{provider: provider} do
+ changeset =
+ %Auth.Identity{}
+ |> Ecto.Changeset.change(
+ provider_virtual_state: %{
+ expires_at: DateTime.utc_now()
+ }
+ )
+
+ assert changeset = identity_changeset(provider, changeset)
+
+ refute changeset.valid?
+
+ assert %{
+ provider_virtual_state: %{
+ expires_at: ["must be greater than " <> _]
+ }
+ } = errors_on(changeset)
+ end
+
+ test "trims provider identifier", %{provider: provider} do
+ changeset =
+ %Auth.Identity{}
+ |> Ecto.Changeset.change(
+ provider_identifier: " X ",
+ provider_virtual_state: %{
+ expires_at: DateTime.utc_now() |> DateTime.add(1, :day)
+ }
+ )
+
+ assert %Ecto.Changeset{} = changeset = identity_changeset(provider, changeset)
+ assert changeset.changes.provider_identifier == "X"
+ end
+ end
+
+ describe "ensure_provisioned/1" do
+ test "returns changeset as is" do
+ changeset = %Ecto.Changeset{}
+ assert ensure_provisioned(changeset) == changeset
+ end
+ end
+
+ describe "ensure_deprovisioned/1" do
+ test "returns changeset as is" do
+ changeset = %Ecto.Changeset{}
+ assert ensure_deprovisioned(changeset) == changeset
+ end
+ end
+
+ describe "verify_secret/2" do
+ setup do
+ account = AccountsFixtures.create_account()
+ provider = AuthFixtures.create_token_provider(account: account)
+
+ identity =
+ AuthFixtures.create_identity(
+ account: account,
+ provider: provider,
+ provider_virtual_state: %{
+ "expires_at" => DateTime.utc_now() |> DateTime.add(1, :day)
+ }
+ )
+
+ %{
+ account: account,
+ provider: provider,
+ identity: identity
+ }
+ end
+
+ test "returns :invalid_secret on invalid secret", %{identity: identity} do
+ assert verify_secret(identity, "foo") == {:error, :invalid_secret}
+ end
+
+ test "returns :expired_secret on expires secret", %{identity: identity} do
+ identity =
+ identity
+ |> Ecto.Changeset.change(
+ provider_state: %{
+ "expires_at" => DateTime.utc_now() |> DateTime.add(-1, :second),
+ "secret_hash" => Domain.Crypto.hash("foo")
+ }
+ )
+ |> Repo.update!()
+
+ assert verify_secret(identity, identity.provider_virtual_state.secret) ==
+ {:error, :expired_secret}
+ end
+
+ test "returns :ok on valid secret", %{identity: identity} do
+ assert {:ok, verified_identity, expires_at} =
+ verify_secret(identity, identity.provider_virtual_state.secret)
+
+ assert verified_identity.provider_state["secret_hash"] ==
+ identity.provider_state["secret_hash"]
+
+ assert verified_identity.provider_state["expires_at"] ==
+ identity.provider_state["expires_at"]
+
+ assert {:ok, ^expires_at, 0} = DateTime.from_iso8601(identity.provider_state["expires_at"])
+ end
+ end
+end
diff --git a/apps/domain/test/domain/auth/adapters/userpass_test.exs b/apps/domain/test/domain/auth/adapters/userpass_test.exs
index b38516969..5d7060934 100644
--- a/apps/domain/test/domain/auth/adapters/userpass_test.exs
+++ b/apps/domain/test/domain/auth/adapters/userpass_test.exs
@@ -28,7 +28,7 @@ defmodule Domain.Auth.Adapters.UserPassTest do
assert %Ecto.Changeset{} = changeset = identity_changeset(provider, changeset)
assert %{provider_state: state, provider_virtual_state: virtual_state} = changeset.changes
- assert %{password_hash: password_hash} = state
+ assert %{"password_hash" => password_hash} = state
assert Domain.Crypto.equal?("Firezone1234", password_hash)
assert virtual_state == %{}
@@ -135,7 +135,7 @@ defmodule Domain.Auth.Adapters.UserPassTest do
assert {:ok, verified_identity, nil} = verify_secret(identity, "Firezone1234")
assert verified_identity.provider_state["password_hash"] ==
- identity.provider_state.password_hash
+ identity.provider_state["password_hash"]
end
end
end
diff --git a/apps/domain/test/domain/auth/oidc/refresher_test.exs b/apps/domain/test/domain/auth/oidc/refresher_test.exs
index fd0d0beb1..ede48e383 100644
--- a/apps/domain/test/domain/auth/oidc/refresher_test.exs
+++ b/apps/domain/test/domain/auth/oidc/refresher_test.exs
@@ -4,8 +4,8 @@
# alias Domain.UsersFixtures
# setup do
-# user = UsersFixtures.create_user_with_role(:admin)
-# {bypass, [provider_attrs]} = Domain.ConfigFixtures.start_openid_providers(["google"])
+# user = UsersFixtures.create_user_with_role(:account_admin_user)
+# {bypass, [provider_attrs]} = Domain.AuthFixtures.start_openid_providers(["google"])
# conn =
# Repo.insert!(%Domain.Auth.OIDC.Connection{
@@ -19,7 +19,7 @@
# describe "refresh failed" do
# test "disable user", %{user: user, conn: conn, bypass: bypass} do
-# Domain.ConfigFixtures.expect_refresh_token_failure(bypass)
+# Domain.AuthFixtures.expect_refresh_token_failure(bypass)
# assert Refresher.refresh(user.id) == {:stop, :shutdown, user.id}
# user = Repo.reload(user)
@@ -32,7 +32,7 @@
# describe "refresh succeeded" do
# test "does not change user", %{user: user, conn: conn, bypass: bypass} do
-# Domain.ConfigFixtures.expect_refresh_token(bypass)
+# Domain.AuthFixtures.expect_refresh_token(bypass)
# assert Refresher.refresh(user.id) == {:stop, :shutdown, user.id}
# user = Repo.reload(user)
diff --git a/apps/domain/test/domain/auth_test.exs b/apps/domain/test/domain/auth_test.exs
index b8b50b546..ae156c14e 100644
--- a/apps/domain/test/domain/auth_test.exs
+++ b/apps/domain/test/domain/auth_test.exs
@@ -99,7 +99,7 @@ defmodule Domain.AuthTest do
account: other_account
} do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(account: account, role: :admin)
+ actor = ActorsFixtures.create_actor(account: account, type: :account_admin_user)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -111,7 +111,14 @@ defmodule Domain.AuthTest do
setup do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_email_provider(account: account)
- actor = ActorsFixtures.create_actor(role: :admin, account: account, provider: provider)
+
+ actor =
+ ActorsFixtures.create_actor(
+ type: :account_admin_user,
+ account: account,
+ provider: provider
+ )
+
identity = AuthFixtures.create_identity(account: account, provider: provider, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -227,7 +234,7 @@ defmodule Domain.AuthTest do
describe "enable_provider/2" do
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -285,7 +292,14 @@ defmodule Domain.AuthTest do
setup do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_email_provider(account: account)
- actor = ActorsFixtures.create_actor(role: :admin, account: account, provider: provider)
+
+ actor =
+ ActorsFixtures.create_actor(
+ type: :account_admin_user,
+ account: account,
+ provider: provider
+ )
+
identity = AuthFixtures.create_identity(account: account, provider: provider, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -415,14 +429,23 @@ defmodule Domain.AuthTest do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_email_provider(account: account)
provider_identifier = AuthFixtures.random_provider_identifier(provider)
- actor = ActorsFixtures.create_actor(role: :admin, account: account, provider: provider)
+
+ actor =
+ ActorsFixtures.create_actor(
+ type: :account_admin_user,
+ account: account,
+ provider: provider
+ )
assert {:ok, identity} = create_identity(actor, provider, provider_identifier)
assert identity.provider_id == provider.id
assert identity.provider_identifier == provider_identifier
assert identity.actor_id == actor.id
- assert %{sign_in_token_created_at: _, sign_in_token_hash: _} = identity.provider_state
+
+ assert %{"sign_in_token_created_at" => _, "sign_in_token_hash" => _} =
+ identity.provider_state
+
assert %{sign_in_token: _} = identity.provider_virtual_state
assert identity.account_id == provider.account_id
assert is_nil(identity.deleted_at)
@@ -431,7 +454,13 @@ defmodule Domain.AuthTest do
test "returns error when identifier is invalid" do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_email_provider(account: account)
- actor = ActorsFixtures.create_actor(role: :admin, account: account, provider: provider)
+
+ actor =
+ ActorsFixtures.create_actor(
+ type: :account_admin_user,
+ account: account,
+ provider: provider
+ )
provider_identifier = Ecto.UUID.generate()
assert {:error, changeset} = create_identity(actor, provider, provider_identifier)
@@ -447,7 +476,14 @@ defmodule Domain.AuthTest do
setup do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_email_provider(account: account)
- actor = ActorsFixtures.create_actor(role: :admin, account: account, provider: provider)
+
+ actor =
+ ActorsFixtures.create_actor(
+ type: :account_admin_user,
+ account: account,
+ provider: provider
+ )
+
identity = AuthFixtures.create_identity(account: account, provider: provider, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -493,7 +529,10 @@ defmodule Domain.AuthTest do
assert new_identity.provider_identifier == provider_identifier
assert new_identity.provider_id == identity.provider_id
assert new_identity.actor_id == identity.actor_id
- assert %{sign_in_token_created_at: _, sign_in_token_hash: _} = new_identity.provider_state
+
+ assert %{"sign_in_token_created_at" => _, "sign_in_token_hash" => _} =
+ new_identity.provider_state
+
assert %{sign_in_token: _} = new_identity.provider_virtual_state
assert new_identity.account_id == identity.account_id
assert is_nil(new_identity.deleted_at)
@@ -526,7 +565,14 @@ defmodule Domain.AuthTest do
setup do
account = AccountsFixtures.create_account()
provider = AuthFixtures.create_email_provider(account: account)
- actor = ActorsFixtures.create_actor(role: :admin, account: account, provider: provider)
+
+ actor =
+ ActorsFixtures.create_actor(
+ type: :account_admin_user,
+ account: account,
+ provider: provider
+ )
+
identity = AuthFixtures.create_identity(account: account, provider: provider, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -691,13 +737,13 @@ defmodule Domain.AuthTest do
assert subject.context.user_agent == user_agent
end
- test "returned subject expiration depends on user role", %{
+ test "returned subject expiration depends on user type", %{
account: account,
provider: provider,
user_agent: user_agent,
remote_ip: remote_ip
} do
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider, actor: actor)
secret = identity.provider_virtual_state.sign_in_token
@@ -707,7 +753,7 @@ defmodule Domain.AuthTest do
three_hours = 3 * 60 * 60
assert_datetime_diff(subject.expires_at, DateTime.utc_now(), three_hours)
- actor = ActorsFixtures.create_actor(role: :unprivileged, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_user, account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider, actor: actor)
secret = identity.provider_virtual_state.sign_in_token
@@ -724,7 +770,7 @@ defmodule Domain.AuthTest do
user_agent: user_agent,
remote_ip: remote_ip
} do
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, _provider} = disable_provider(provider, subject)
@@ -742,7 +788,7 @@ defmodule Domain.AuthTest do
user_agent: user_agent,
remote_ip: remote_ip
} do
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, provider: provider, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, _provider} = delete_provider(provider, subject)
@@ -805,7 +851,7 @@ defmodule Domain.AuthTest do
{:error, :unauthorized}
end
- test "returns subject on success", %{
+ test "returns subject on success for session token", %{
subject: subject,
user_agent: user_agent,
remote_ip: remote_ip
@@ -821,6 +867,37 @@ defmodule Domain.AuthTest do
assert DateTime.diff(reconstructed_subject.expires_at, subject.expires_at) <= 1
end
+ test "returns subject on success for service account token", %{
+ account: account,
+ user_agent: user_agent,
+ remote_ip: remote_ip,
+ subject: subject
+ } do
+ one_day = DateTime.utc_now() |> DateTime.add(1, :day)
+ provider = AuthFixtures.create_token_provider(account: account)
+
+ identity =
+ AuthFixtures.create_identity(
+ account: account,
+ provider: provider,
+ user_agent: user_agent,
+ remote_ip: remote_ip,
+ provider_virtual_state: %{
+ "expires_at" => one_day
+ }
+ )
+
+ {:ok, token} = create_access_token_for_identity(identity)
+
+ assert {:ok, reconstructed_subject} = sign_in(token, user_agent, remote_ip)
+ assert reconstructed_subject.identity.id == identity.id
+ assert reconstructed_subject.actor.id == identity.actor_id
+ assert reconstructed_subject.account.id == identity.account_id
+ assert reconstructed_subject.permissions == subject.permissions
+ assert reconstructed_subject.context == subject.context
+ assert DateTime.diff(reconstructed_subject.expires_at, one_day) <= 1
+ end
+
test "updates last signed in fields for identity on success", %{
identity: identity,
subject: subject,
@@ -888,7 +965,7 @@ defmodule Domain.AuthTest do
describe "has_permission?/2" do
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -926,9 +1003,9 @@ defmodule Domain.AuthTest do
end
end
- describe "fetch_role_permissions!/1" do
- test "returns permissions for given role" do
- permissions = fetch_role_permissions!(:admin)
+ describe "fetch_type_permissions!/1" do
+ test "returns permissions for given type" do
+ permissions = fetch_type_permissions!(:account_admin_user)
assert Enum.count(permissions) > 0
end
end
@@ -948,7 +1025,7 @@ defmodule Domain.AuthTest do
describe "ensure_has_access_to/2" do
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -978,7 +1055,7 @@ defmodule Domain.AuthTest do
describe "ensure_has_permissions/2" do
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
diff --git a/apps/domain/test/domain/clients_test.exs b/apps/domain/test/domain/clients_test.exs
deleted file mode 100644
index 878ab0b51..000000000
--- a/apps/domain/test/domain/clients_test.exs
+++ /dev/null
@@ -1,675 +0,0 @@
-defmodule Domain.ClientsTest do
- use Domain.DataCase, async: true
- import Domain.Clients
- alias Domain.AccountsFixtures
- alias Domain.{NetworkFixtures, ActorsFixtures, AuthFixtures, ClientsFixtures}
- alias Domain.Clients
-
- setup do
- account = AccountsFixtures.create_account()
-
- unprivileged_actor = ActorsFixtures.create_actor(role: :unprivileged, account: account)
-
- unprivileged_identity =
- AuthFixtures.create_identity(account: account, actor: unprivileged_actor)
-
- unprivileged_subject = AuthFixtures.create_subject(unprivileged_identity)
-
- admin_actor = ActorsFixtures.create_actor(role: :admin, account: account)
- admin_identity = AuthFixtures.create_identity(account: account, actor: admin_actor)
- admin_subject = AuthFixtures.create_subject(admin_identity)
-
- %{
- account: account,
- unprivileged_actor: unprivileged_actor,
- unprivileged_identity: unprivileged_identity,
- unprivileged_subject: unprivileged_subject,
- admin_actor: admin_actor,
- admin_identity: admin_identity,
- admin_subject: admin_subject
- }
- end
-
- describe "count_by_account_id/0" do
- test "counts clients for an account", %{account: account} do
- ClientsFixtures.create_client(account: account)
- ClientsFixtures.create_client(account: account)
- ClientsFixtures.create_client(account: account)
- ClientsFixtures.create_client()
-
- assert count_by_account_id(account.id) == 3
- end
- end
-
- describe "count_by_actor_id/1" do
- test "returns 0 if actor does not exist" do
- assert count_by_actor_id(Ecto.UUID.generate()) == 0
- end
-
- test "returns count of clients for a actor" do
- client = ClientsFixtures.create_client()
- assert count_by_actor_id(client.actor_id) == 1
- end
- end
-
- describe "fetch_client_by_id/2" do
- test "returns error when UUID is invalid", %{unprivileged_subject: subject} do
- assert fetch_client_by_id("foo", subject) == {:error, :not_found}
- end
-
- test "does not return deleted clients", %{
- unprivileged_actor: actor,
- unprivileged_subject: subject
- } do
- client =
- ClientsFixtures.create_client(actor: actor)
- |> ClientsFixtures.delete_client()
-
- assert fetch_client_by_id(client.id, subject) == {:error, :not_found}
- end
-
- test "returns client by id", %{unprivileged_actor: actor, unprivileged_subject: subject} do
- client = ClientsFixtures.create_client(actor: actor)
- assert fetch_client_by_id(client.id, subject) == {:ok, client}
- end
-
- test "returns client that belongs to another actor with manage permission", %{
- account: account,
- unprivileged_subject: subject
- } do
- client = ClientsFixtures.create_client(account: account)
-
- subject =
- subject
- |> AuthFixtures.remove_permissions()
- |> AuthFixtures.add_permission(Clients.Authorizer.manage_clients_permission())
-
- assert fetch_client_by_id(client.id, subject) == {:ok, client}
- end
-
- test "does not returns client that belongs to another account with manage permission", %{
- unprivileged_subject: subject
- } do
- client = ClientsFixtures.create_client()
-
- subject =
- subject
- |> AuthFixtures.remove_permissions()
- |> AuthFixtures.add_permission(Clients.Authorizer.manage_clients_permission())
-
- assert fetch_client_by_id(client.id, subject) == {:error, :not_found}
- end
-
- test "does not return client that belongs to another actor with manage_own permission", %{
- unprivileged_subject: subject
- } do
- client = ClientsFixtures.create_client()
-
- subject =
- subject
- |> AuthFixtures.remove_permissions()
- |> AuthFixtures.add_permission(Clients.Authorizer.manage_own_clients_permission())
-
- assert fetch_client_by_id(client.id, subject) == {:error, :not_found}
- end
-
- test "returns error when client does not exist", %{unprivileged_subject: subject} do
- assert fetch_client_by_id(Ecto.UUID.generate(), subject) ==
- {:error, :not_found}
- end
-
- test "returns error when subject has no permission to view clients", %{
- unprivileged_subject: subject
- } do
- subject = AuthFixtures.remove_permissions(subject)
-
- assert fetch_client_by_id(Ecto.UUID.generate(), subject) ==
- {:error,
- {:unauthorized,
- [
- missing_permissions: [
- {:one_of,
- [
- Clients.Authorizer.manage_clients_permission(),
- Clients.Authorizer.manage_own_clients_permission()
- ]}
- ]
- ]}}
- end
- end
-
- describe "list_clients/1" do
- test "returns empty list when there are no clients", %{admin_subject: subject} do
- assert list_clients(subject) == {:ok, []}
- end
-
- test "does not list deleted clients", %{
- unprivileged_actor: actor,
- unprivileged_subject: subject
- } do
- ClientsFixtures.create_client(actor: actor)
- |> ClientsFixtures.delete_client()
-
- assert list_clients(subject) == {:ok, []}
- end
-
- test "does not list clients in other accounts", %{
- unprivileged_subject: subject
- } do
- ClientsFixtures.create_client()
-
- assert list_clients(subject) == {:ok, []}
- end
-
- test "shows all clients owned by a actor for unprivileged subject", %{
- unprivileged_actor: actor,
- admin_actor: other_actor,
- unprivileged_subject: subject
- } do
- client = ClientsFixtures.create_client(actor: actor)
- ClientsFixtures.create_client(actor: other_actor)
-
- assert list_clients(subject) == {:ok, [client]}
- end
-
- test "shows all clients for admin subject", %{
- unprivileged_actor: other_actor,
- admin_actor: admin_actor,
- admin_subject: subject
- } do
- ClientsFixtures.create_client(actor: admin_actor)
- ClientsFixtures.create_client(actor: other_actor)
-
- assert {:ok, clients} = list_clients(subject)
- assert length(clients) == 2
- end
-
- test "returns error when subject has no permission to manage clients", %{
- unprivileged_subject: subject
- } do
- subject = AuthFixtures.remove_permissions(subject)
-
- assert list_clients(subject) ==
- {:error,
- {:unauthorized,
- [
- missing_permissions: [
- {:one_of,
- [
- Clients.Authorizer.manage_clients_permission(),
- Clients.Authorizer.manage_own_clients_permission()
- ]}
- ]
- ]}}
- end
- end
-
- describe "list_clients_by_actor_id/2" do
- test "returns empty list when there are no clients for a given actor", %{
- admin_actor: actor,
- admin_subject: subject
- } do
- assert list_clients_by_actor_id(Ecto.UUID.generate(), subject) == {:ok, []}
- assert list_clients_by_actor_id(actor.id, subject) == {:ok, []}
- ClientsFixtures.create_client()
- assert list_clients_by_actor_id(actor.id, subject) == {:ok, []}
- end
-
- test "returns error when actor id is invalid", %{admin_subject: subject} do
- assert list_clients_by_actor_id("foo", subject) == {:error, :not_found}
- end
-
- test "does not list deleted clients", %{
- unprivileged_actor: actor,
- unprivileged_identity: identity,
- unprivileged_subject: subject
- } do
- ClientsFixtures.create_client(identity: identity)
- |> ClientsFixtures.delete_client()
-
- assert list_clients_by_actor_id(actor.id, subject) == {:ok, []}
- end
-
- test "does not deleted clients for actors in other accounts", %{
- unprivileged_subject: unprivileged_subject,
- admin_subject: admin_subject
- } do
- actor = ActorsFixtures.create_actor(role: :unprivileged)
- ClientsFixtures.create_client(actor: actor)
-
- assert list_clients_by_actor_id(actor.id, unprivileged_subject) == {:ok, []}
- assert list_clients_by_actor_id(actor.id, admin_subject) == {:ok, []}
- end
-
- test "shows only clients owned by a actor for unprivileged subject", %{
- unprivileged_actor: actor,
- admin_actor: other_actor,
- unprivileged_subject: subject
- } do
- client = ClientsFixtures.create_client(actor: actor)
- ClientsFixtures.create_client(actor: other_actor)
-
- assert list_clients_by_actor_id(actor.id, subject) == {:ok, [client]}
- assert list_clients_by_actor_id(other_actor.id, subject) == {:ok, []}
- end
-
- test "shows all clients owned by another actor for admin subject", %{
- unprivileged_actor: other_actor,
- admin_actor: admin_actor,
- admin_subject: subject
- } do
- ClientsFixtures.create_client(actor: admin_actor)
- ClientsFixtures.create_client(actor: other_actor)
-
- assert {:ok, [_client]} = list_clients_by_actor_id(admin_actor.id, subject)
- assert {:ok, [_client]} = list_clients_by_actor_id(other_actor.id, subject)
- end
-
- test "returns error when subject has no permission to manage clients", %{
- unprivileged_subject: subject
- } do
- subject = AuthFixtures.remove_permissions(subject)
-
- assert list_clients_by_actor_id(Ecto.UUID.generate(), subject) ==
- {:error,
- {:unauthorized,
- [
- missing_permissions: [
- {:one_of,
- [
- Clients.Authorizer.manage_clients_permission(),
- Clients.Authorizer.manage_own_clients_permission()
- ]}
- ]
- ]}}
- end
- end
-
- describe "change_client/1" do
- test "returns changeset with given changes", %{admin_actor: actor} do
- client = ClientsFixtures.create_client(actor: actor)
- client_attrs = ClientsFixtures.client_attrs()
-
- assert changeset = change_client(client, client_attrs)
- assert %Ecto.Changeset{data: %Domain.Clients.Client{}} = changeset
-
- assert changeset.changes == %{name: client_attrs.name}
- end
- end
-
- describe "upsert_client/2" do
- test "returns errors on invalid attrs", %{
- admin_subject: subject
- } do
- attrs = %{
- external_id: nil,
- public_key: "x",
- ipv4: "1.1.1.256",
- ipv6: "fd01::10000"
- }
-
- assert {:error, changeset} = upsert_client(attrs, subject)
-
- assert errors_on(changeset) == %{
- public_key: ["should be 44 character(s)", "must be a base64-encoded string"],
- external_id: ["can't be blank"]
- }
- end
-
- test "allows creating client with just required attributes", %{
- admin_actor: actor,
- admin_identity: identity,
- admin_subject: subject
- } do
- attrs =
- ClientsFixtures.client_attrs()
- |> Map.delete(:name)
-
- assert {:ok, client} = upsert_client(attrs, subject)
-
- assert client.name
-
- assert client.public_key == attrs.public_key
-
- assert client.actor_id == actor.id
- assert client.identity_id == identity.id
- assert client.account_id == actor.account_id
-
- refute is_nil(client.ipv4)
- refute is_nil(client.ipv6)
-
- assert client.last_seen_remote_ip == %Postgrex.INET{address: subject.context.remote_ip}
- assert client.last_seen_user_agent == subject.context.user_agent
- assert client.last_seen_version == "0.7.412"
- assert client.last_seen_at
- end
-
- test "updates client when it already exists", %{
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client(subject: subject)
- attrs = ClientsFixtures.client_attrs(external_id: client.external_id)
-
- subject = %{
- subject
- | context: %Domain.Auth.Context{
- subject.context
- | remote_ip: {100, 64, 100, 101},
- user_agent: "iOS/12.5 (iPhone) connlib/0.7.411"
- }
- }
-
- assert {:ok, updated_client} = upsert_client(attrs, subject)
-
- assert Repo.aggregate(Clients.Client, :count, :id) == 1
-
- assert updated_client.name
- assert updated_client.last_seen_remote_ip.address == subject.context.remote_ip
- assert updated_client.last_seen_remote_ip != client.last_seen_remote_ip
- assert updated_client.last_seen_user_agent == subject.context.user_agent
- assert updated_client.last_seen_user_agent != client.last_seen_user_agent
- assert updated_client.last_seen_version == "0.7.411"
- assert updated_client.public_key != client.public_key
- assert updated_client.public_key == attrs.public_key
-
- assert updated_client.actor_id == client.actor_id
- assert updated_client.identity_id == client.identity_id
- assert updated_client.ipv4 == client.ipv4
- assert updated_client.ipv6 == client.ipv6
- assert updated_client.last_seen_at
- assert updated_client.last_seen_at != client.last_seen_at
- end
-
- test "does not reserve additional addresses on update", %{
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client(subject: subject)
-
- attrs =
- ClientsFixtures.client_attrs(
- external_id: client.external_id,
- last_seen_user_agent: "iOS/12.5 (iPhone) connlib/0.7.411",
- last_seen_remote_ip: %Postgrex.INET{address: {100, 64, 100, 100}}
- )
-
- assert {:ok, updated_client} = upsert_client(attrs, subject)
-
- addresses =
- Domain.Network.Address
- |> Repo.all()
- |> Enum.map(fn %Domain.Network.Address{address: address, type: type} ->
- %{address: address, type: type}
- end)
-
- assert length(addresses) == 2
- assert %{address: updated_client.ipv4, type: :ipv4} in addresses
- assert %{address: updated_client.ipv6, type: :ipv6} in addresses
- end
-
- test "allows unprivileged actor to create a client for himself", %{
- admin_subject: subject
- } do
- attrs =
- ClientsFixtures.client_attrs()
- |> Map.delete(:name)
-
- assert {:ok, _client} = upsert_client(attrs, subject)
- end
-
- test "does not allow to reuse IP addresses", %{
- account: account,
- admin_subject: subject
- } do
- attrs = ClientsFixtures.client_attrs(account: account)
- assert {:ok, client} = upsert_client(attrs, subject)
-
- addresses =
- Domain.Network.Address
- |> Repo.all()
- |> Enum.map(fn %Domain.Network.Address{address: address, type: type} ->
- %{address: address, type: type}
- end)
-
- assert length(addresses) == 2
- assert %{address: client.ipv4, type: :ipv4} in addresses
- assert %{address: client.ipv6, type: :ipv6} in addresses
-
- assert_raise Ecto.ConstraintError, fn ->
- NetworkFixtures.create_address(address: client.ipv4, account: account)
- end
-
- assert_raise Ecto.ConstraintError, fn ->
- NetworkFixtures.create_address(address: client.ipv6, account: account)
- end
- end
-
- test "ip addresses are unique per account", %{
- account: account,
- admin_subject: subject
- } do
- attrs = ClientsFixtures.client_attrs(account: account)
- assert {:ok, client} = upsert_client(attrs, subject)
-
- assert %Domain.Network.Address{} = NetworkFixtures.create_address(address: client.ipv4)
- assert %Domain.Network.Address{} = NetworkFixtures.create_address(address: client.ipv6)
- end
-
- test "returns error when subject has no permission to create clients", %{
- admin_subject: subject
- } do
- subject = AuthFixtures.remove_permissions(subject)
-
- assert upsert_client(%{}, subject) ==
- {:error,
- {:unauthorized,
- [missing_permissions: [Clients.Authorizer.manage_own_clients_permission()]]}}
- end
- end
-
- describe "update_client/3" do
- test "allows admin actor to update own clients", %{admin_actor: actor, admin_subject: subject} do
- client = ClientsFixtures.create_client(actor: actor)
- attrs = %{name: "new name"}
-
- assert {:ok, client} = update_client(client, attrs, subject)
-
- assert client.name == attrs.name
- end
-
- test "allows admin actor to update other actors clients", %{
- account: account,
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client(account: account)
- attrs = %{name: "new name"}
-
- assert {:ok, client} = update_client(client, attrs, subject)
-
- assert client.name == attrs.name
- end
-
- test "allows unprivileged actor to update own clients", %{
- unprivileged_actor: actor,
- unprivileged_subject: subject
- } do
- client = ClientsFixtures.create_client(actor: actor)
- attrs = %{name: "new name"}
-
- assert {:ok, client} = update_client(client, attrs, subject)
-
- assert client.name == attrs.name
- end
-
- test "does not allow unprivileged actor to update other actors clients", %{
- account: account,
- unprivileged_subject: subject
- } do
- client = ClientsFixtures.create_client(account: account)
- attrs = %{name: "new name"}
-
- assert update_client(client, attrs, subject) ==
- {:error,
- {:unauthorized,
- [missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
- end
-
- test "does not allow admin actor to update clients in other accounts", %{
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client()
- attrs = %{name: "new name"}
-
- assert update_client(client, attrs, subject) == {:error, :not_found}
- end
-
- test "does not allow to reset required fields to empty values", %{
- admin_actor: actor,
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client(actor: actor)
- attrs = %{name: nil, public_key: nil}
-
- assert {:error, changeset} = update_client(client, attrs, subject)
-
- assert errors_on(changeset) == %{name: ["can't be blank"]}
- end
-
- test "returns error on invalid attrs", %{admin_actor: actor, admin_subject: subject} do
- client = ClientsFixtures.create_client(actor: actor)
-
- attrs = %{
- name: String.duplicate("a", 256)
- }
-
- assert {:error, changeset} = update_client(client, attrs, subject)
-
- assert errors_on(changeset) == %{
- name: ["should be at most 255 character(s)"]
- }
- end
-
- test "ignores updates for any field except name", %{
- admin_actor: actor,
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client(actor: actor)
-
- fields = Clients.Client.__schema__(:fields) -- [:name]
- value = -1
-
- for field <- fields do
- assert {:ok, updated_client} = update_client(client, %{field => value}, subject)
- assert updated_client == client
- end
- end
-
- test "returns error when subject has no permission to update clients", %{
- admin_actor: actor,
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client(actor: actor)
-
- subject = AuthFixtures.remove_permissions(subject)
-
- assert update_client(client, %{}, subject) ==
- {:error,
- {:unauthorized,
- [missing_permissions: [Clients.Authorizer.manage_own_clients_permission()]]}}
-
- client = ClientsFixtures.create_client()
-
- assert update_client(client, %{}, subject) ==
- {:error,
- {:unauthorized,
- [missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
- end
- end
-
- describe "delete_client/2" do
- test "returns error on state conflict", %{admin_actor: actor, admin_subject: subject} do
- client = ClientsFixtures.create_client(actor: actor)
-
- assert {:ok, deleted} = delete_client(client, subject)
- assert delete_client(deleted, subject) == {:error, :not_found}
- assert delete_client(client, subject) == {:error, :not_found}
- end
-
- test "admin can delete own clients", %{admin_actor: actor, admin_subject: subject} do
- client = ClientsFixtures.create_client(actor: actor)
-
- assert {:ok, deleted} = delete_client(client, subject)
- assert deleted.deleted_at
- end
-
- test "admin can delete other people clients", %{
- unprivileged_actor: actor,
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client(actor: actor)
-
- assert {:ok, deleted} = delete_client(client, subject)
- assert deleted.deleted_at
- end
-
- test "admin can not delete clients in other accounts", %{
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client()
-
- assert delete_client(client, subject) == {:error, :not_found}
- end
-
- test "unprivileged can delete own clients", %{
- account: account,
- unprivileged_actor: actor,
- unprivileged_subject: subject
- } do
- client = ClientsFixtures.create_client(account: account, actor: actor)
-
- assert {:ok, deleted} = delete_client(client, subject)
- assert deleted.deleted_at
- end
-
- test "unprivileged can not delete other people clients", %{
- account: account,
- unprivileged_subject: subject
- } do
- client = ClientsFixtures.create_client()
-
- assert delete_client(client, subject) ==
- {:error,
- {:unauthorized,
- [missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
-
- client = ClientsFixtures.create_client(account: account)
-
- assert delete_client(client, subject) ==
- {:error,
- {:unauthorized,
- [missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
-
- assert Repo.aggregate(Clients.Client, :count) == 2
- end
-
- test "returns error when subject has no permission to delete clients", %{
- admin_actor: actor,
- admin_subject: subject
- } do
- client = ClientsFixtures.create_client(actor: actor)
-
- subject = AuthFixtures.remove_permissions(subject)
-
- assert delete_client(client, subject) ==
- {:error,
- {:unauthorized,
- [missing_permissions: [Clients.Authorizer.manage_own_clients_permission()]]}}
-
- client = ClientsFixtures.create_client()
-
- assert delete_client(client, subject) ==
- {:error,
- {:unauthorized,
- [missing_permissions: [Clients.Authorizer.manage_clients_permission()]]}}
- end
- end
-end
diff --git a/apps/domain/test/domain/config/definition_test.exs b/apps/domain/test/domain/config/definition_test.exs
index 754a83d3f..776f07e10 100644
--- a/apps/domain/test/domain/config/definition_test.exs
+++ b/apps/domain/test/domain/config/definition_test.exs
@@ -57,8 +57,8 @@ defmodule Domain.Config.DefinitionTest do
end
test "inserts a function which returns definition doc" do
- assert fetch_doc(Domain.Config.Definitions, :default_admin_email) ==
- {:ok, "Primary administrator email.\n"}
+ assert {:ok, doc} = fetch_doc(Domain.Config.Definitions, :devices_upstream_dns)
+ assert doc =~ "Comma-separated list of upstream DNS servers to use for devices."
assert fetch_doc(Foo, :bar) ==
{:error, :module_not_found}
diff --git a/apps/domain/test/domain/config/resolver_test.exs b/apps/domain/test/domain/config/resolver_test.exs
index 398972b9d..b24d38567 100644
--- a/apps/domain/test/domain/config/resolver_test.exs
+++ b/apps/domain/test/domain/config/resolver_test.exs
@@ -37,10 +37,10 @@ defmodule Domain.Config.ResolverTest do
test "returns variable from database" do
env_configurations = %{}
- db_configurations = %Domain.Config.Configuration{default_client_dns: "1.2.3.4"}
+ db_configurations = %Domain.Config.Configuration{devices_upstream_dns: "1.2.3.4"}
- assert resolve(:default_client_dns, env_configurations, db_configurations, []) ==
- {:ok, {{:db, :default_client_dns}, "1.2.3.4"}}
+ assert resolve(:devices_upstream_dns, env_configurations, db_configurations, []) ==
+ {:ok, {{:db, :devices_upstream_dns}, "1.2.3.4"}}
end
test "precedence" do
diff --git a/apps/domain/test/domain/config_test.exs b/apps/domain/test/domain/config_test.exs
index 6c456ca9d..99ab6346d 100644
--- a/apps/domain/test/domain/config_test.exs
+++ b/apps/domain/test/domain/config_test.exs
@@ -1,6 +1,9 @@
defmodule Domain.ConfigTest do
use Domain.DataCase, async: true
import Domain.Config
+ alias Domain.Config
+ alias Domain.{AccountsFixtures, AuthFixtures, ActorsFixtures}
+ alias Domain.ConfigFixtures
defmodule Test do
use Domain.Config.Definition
@@ -80,13 +83,22 @@ defmodule Domain.ConfigTest do
)
end
- describe "fetch_source_and_config!/1" do
- test "returns source and config value" do
- assert fetch_source_and_config!(:default_client_mtu) ==
- {{:db, :default_client_mtu}, 1280}
+ describe "fetch_resolved_configs!/1" do
+ setup do
+ account = AccountsFixtures.create_account()
+ ConfigFixtures.upsert_configuration(account: account)
+
+ %{account: account}
end
- test "raises an error when value is missing" do
+ test "returns source and config values", %{account: account} do
+ assert fetch_resolved_configs!(account.id, [:devices_upstream_dns, :devices_upstream_dns]) ==
+ %{
+ devices_upstream_dns: [%Postgrex.INET{address: {1, 1, 1, 1}, netmask: nil}]
+ }
+ end
+
+ test "raises an error when value is missing", %{account: account} do
message = """
Missing required configuration value for 'external_url'.
@@ -113,26 +125,29 @@ defmodule Domain.ConfigTest do
"""
assert_raise RuntimeError, message, fn ->
- fetch_source_and_config!(:external_url)
+ fetch_resolved_configs!(account.id, [:external_url])
end
end
end
- describe "fetch_source_and_configs!/1" do
- test "returns source and config values" do
- assert fetch_source_and_configs!([:default_client_mtu, :default_client_dns]) ==
+ describe "fetch_resolved_configs_with_sources!/1" do
+ setup do
+ account = AccountsFixtures.create_account()
+ ConfigFixtures.upsert_configuration(account: account)
+
+ %{account: account}
+ end
+
+ test "returns source and config values", %{account: account} do
+ assert fetch_resolved_configs_with_sources!(account.id, [:devices_upstream_dns]) ==
%{
- default_client_dns:
- {{:db, :default_client_dns},
- [
- %Postgrex.INET{address: {1, 1, 1, 1}, netmask: nil},
- %Postgrex.INET{address: {1, 0, 0, 1}, netmask: nil}
- ]},
- default_client_mtu: {{:db, :default_client_mtu}, 1280}
+ devices_upstream_dns:
+ {{:db, :devices_upstream_dns},
+ [%Postgrex.INET{address: {1, 1, 1, 1}, netmask: nil}]}
}
end
- test "raises an error when value is missing" do
+ test "raises an error when value is missing", %{account: account} do
message = """
Missing required configuration value for 'external_url'.
@@ -159,11 +174,11 @@ defmodule Domain.ConfigTest do
"""
assert_raise RuntimeError, message, fn ->
- fetch_source_and_configs!([:external_url])
+ fetch_resolved_configs_with_sources!(account.id, [:external_url])
end
end
- test "raises an error when value is invalid" do
+ test "raises an error when value is invalid", %{account: account} do
put_system_env_override(:external_url, "https://example.com/vpn")
message = """
@@ -187,78 +202,7 @@ defmodule Domain.ConfigTest do
"""
assert_raise RuntimeError, message, fn ->
- fetch_source_and_configs!([:external_url])
- end
- end
- end
-
- describe "fetch_config/1" do
- test "returns config value" do
- assert fetch_config(:default_client_mtu) ==
- {:ok, 1280}
- end
-
- test "returns error when value is missing" do
- assert fetch_config(:external_url) ==
- {:error,
- {{nil, ["is required"]},
- [module: Domain.Config.Definitions, key: :external_url, source: :not_found]}}
- end
- end
-
- describe "fetch_config!/1" do
- test "returns config value" do
- assert fetch_config!(:default_client_mtu) ==
- 1280
- end
-
- test "raises when value is missing" do
- assert_raise RuntimeError, fn ->
- fetch_config!(:external_url)
- end
- end
- end
-
- describe "fetch_configs!/1" do
- test "returns source and config values" do
- assert fetch_configs!([:default_client_mtu, :default_client_dns]) ==
- %{
- default_client_dns: [
- %Postgrex.INET{address: {1, 1, 1, 1}, netmask: nil},
- %Postgrex.INET{address: {1, 0, 0, 1}, netmask: nil}
- ],
- default_client_mtu: 1280
- }
- end
-
- test "raises an error when value is missing" do
- message = """
- Missing required configuration value for 'external_url'.
-
- ## How to fix?
-
- ### Using environment variables
-
- You can set this configuration via environment variable by adding it to `.env` file:
-
- EXTERNAL_URL=YOUR_VALUE
-
-
- ## Documentation
-
- The external URL the web UI will be accessible at.
-
- Must be a valid and public FQDN for ACME SSL issuance to function.
-
- You can add a path suffix if you want to serve firezone from a non-root path,
- eg: `https://firezone.mycorp.com/vpn/`.
-
-
- You can find more information on configuration here: https://www.firezone.dev/docs/reference/env-vars/#environment-variable-listing
- """
-
- assert_raise RuntimeError, message, fn ->
- fetch_configs!([:external_url])
+ fetch_resolved_configs_with_sources!(account.id, [:external_url])
end
end
end
@@ -390,246 +334,173 @@ defmodule Domain.ConfigTest do
end
end
- describe "validate_runtime_config!/0" do
- test "raises error on invalid values" do
- message = """
- Found 9 configuration errors:
-
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Missing required configuration value for 'url'.
-
- ## How to fix?
-
- ### Using environment variables
-
- You can set this configuration via environment variable by adding it to `.env` file:
-
- URL=YOUR_VALUE
-
-
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Missing required configuration value for 'boolean'.
-
- ## How to fix?
-
- ### Using environment variables
-
- You can set this configuration via environment variable by adding it to `.env` file:
-
- BOOLEAN=YOUR_VALUE
-
-
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Missing required configuration value for 'json'.
-
- ## How to fix?
-
- ### Using environment variables
-
- You can set this configuration via environment variable by adding it to `.env` file:
-
- JSON=YOUR_VALUE
-
-
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Missing required configuration value for 'json_array'.
-
- ## How to fix?
-
- ### Using environment variables
-
- You can set this configuration via environment variable by adding it to `.env` file:
-
- JSON_ARRAY=YOUR_VALUE
-
-
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Invalid configuration for 'array' retrieved from default value.
-
- Errors:
-
- - `3`: must be less than or equal to 2
-
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Invalid configuration for 'invalid_with_validation' retrieved from default value.
-
- Errors:
-
- - `-1`: must be greater than or equal to 0
-
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Missing required configuration value for 'integer'.
-
- ## How to fix?
-
- ### Using environment variables
-
- You can set this configuration via environment variable by adding it to `.env` file:
-
- INTEGER=YOUR_VALUE
-
-
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Missing required configuration value for 'one_of'.
-
- ## How to fix?
-
- ### Using environment variables
-
- You can set this configuration via environment variable by adding it to `.env` file:
-
- ONE_OF=YOUR_VALUE
-
-
-
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Missing required configuration value for 'required'.
-
- ## How to fix?
-
- ### Using environment variables
-
- You can set this configuration via environment variable by adding it to `.env` file:
-
- REQUIRED=YOUR_VALUE
- """
-
- assert_raise RuntimeError, message, fn ->
- validate_runtime_config!(Test, %{}, %{})
- end
+ describe "get_account_config_by_account_id/1" do
+ setup do
+ account = AccountsFixtures.create_account()
+ %{account: account}
end
- test "returns :ok when config is valid" do
- env_config = %{
- "BOOLEAN" => "true",
- "ARRAY" => "1",
- "JSON" => "{\"foo\":\"bar\"}",
- "JSON_ARRAY" => "[{\"foo\":\"bar\"}]",
- "INTEGER" => "123",
- "ONE_OF" => "a",
- "REQUIRED" => "1.1.1.1",
- "INVALID_WITH_VALIDATION" => "2",
- "URL" => "http://example.com"
+ test "returns configuration for an account if it exists", %{
+ account: account
+ } do
+ configuration = ConfigFixtures.upsert_configuration(account: account)
+ assert get_account_config_by_account_id(account.id) == configuration
+ end
+
+ test "returns default configuration for an account if it does not exist", %{
+ account: account
+ } do
+ assert get_account_config_by_account_id(account.id) == %Domain.Config.Configuration{
+ account_id: account.id,
+ devices_upstream_dns: []
+ }
+ end
+ end
+
+ describe "fetch_account_config/1" do
+ setup do
+ account = AccountsFixtures.create_account()
+
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ identity = AuthFixtures.create_identity(account: account, actor: actor)
+ subject = AuthFixtures.create_subject(identity)
+
+ %{
+ account: account,
+ actor: actor,
+ identity: identity,
+ subject: subject
}
+ end
- assert validate_runtime_config!(Test, %{}, env_config) == :ok
+ test "returns configuration for an account if it exists", %{
+ account: account,
+ subject: subject
+ } do
+ configuration = ConfigFixtures.upsert_configuration(account: account)
+ assert fetch_account_config(subject) == {:ok, configuration}
+ end
+
+ test "returns default configuration for an account if it does not exist", %{
+ account: account,
+ subject: subject
+ } do
+ assert {:ok, config} = fetch_account_config(subject)
+
+ assert config == %Domain.Config.Configuration{
+ account_id: account.id,
+ devices_upstream_dns: []
+ }
+ end
+
+ test "returns error when subject does not have permission to read configuration", %{
+ subject: subject
+ } do
+ subject = AuthFixtures.remove_permissions(subject)
+
+ assert fetch_account_config(subject) ==
+ {:error,
+ {:unauthorized, [missing_permissions: [Config.Authorizer.manage_permission()]]}}
end
end
- describe "fetch_db_config!" do
- test "returns config from db table" do
- assert fetch_db_config!() == Repo.one(Domain.Config.Configuration)
+ describe "change_account_config/2" do
+ setup do
+ account = AccountsFixtures.create_account()
+ configuration = ConfigFixtures.upsert_configuration(account: account)
+
+ %{account: account, configuration: configuration}
+ end
+
+ test "returns config changeset", %{configuration: configuration} do
+ assert %Ecto.Changeset{} = change_account_config(configuration)
end
end
- describe "change_config/2" do
- test "returns config changeset" do
- assert %Ecto.Changeset{} = change_config()
+ describe "update_config/3" do
+ test "returns error when subject can not manage account configuration" do
+ account = AccountsFixtures.create_account()
+ config = get_account_config_by_account_id(account.id)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ identity = AuthFixtures.create_identity(account: account, actor: actor)
+
+ subject =
+ AuthFixtures.create_subject(identity)
+ |> AuthFixtures.remove_permissions()
+
+ assert update_config(config, %{}, subject) ==
+ {:error,
+ {:unauthorized, [missing_permissions: [Config.Authorizer.manage_permission()]]}}
end
end
describe "update_config/2" do
- test "returns error when changeset is invalid" do
- config = Repo.one(Domain.Config.Configuration)
+ setup do
+ account = AccountsFixtures.create_account()
+ %{account: account}
+ end
+
+ test "returns error when changeset is invalid", %{account: account} do
+ config = get_account_config_by_account_id(account.id)
attrs = %{
- local_auth_enabled: 1,
- allow_unprivileged_device_management: 1,
- allow_unprivileged_device_configuration: 1,
- disable_vpn_on_oidc_error: 1,
- default_client_persistent_keepalive: -1,
- default_client_mtu: -1,
- default_client_endpoint: "123",
- default_client_dns: ["!!!"],
- default_client_allowed_ips: ["!"],
- vpn_session_duration: -1
+ devices_upstream_dns: ["!!!"]
}
assert {:error, changeset} = update_config(config, attrs)
assert errors_on(changeset) == %{
- default_client_mtu: ["must be greater than or equal to 576"],
- allow_unprivileged_device_configuration: ["is invalid"],
- allow_unprivileged_device_management: ["is invalid"],
- default_client_allowed_ips: ["is invalid"],
- default_client_dns: [
+ devices_upstream_dns: [
"!!! is not a valid FQDN",
"must be one of: Elixir.Domain.Types.IP, string"
- ],
- default_client_persistent_keepalive: ["must be greater than or equal to 0"],
- local_auth_enabled: ["is invalid"],
- vpn_session_duration: ["must be greater than or equal to 0"]
+ ]
}
end
- test "returns error when trying to change overridden value" do
- put_system_env_override(:local_auth_enabled, false)
+ test "returns error when trying to change overridden value", %{account: account} do
+ put_system_env_override(:devices_upstream_dns, ["1.2.3.4"])
- config = Repo.one(Domain.Config.Configuration)
+ config = get_account_config_by_account_id(account.id)
attrs = %{
- local_auth_enabled: false
+ devices_upstream_dns: ["4.1.2.3"]
}
assert {:error, changeset} = update_config(config, attrs)
assert errors_on(changeset) ==
%{
- local_auth_enabled: [
- "cannot be changed; it is overridden by LOCAL_AUTH_ENABLED environment variable"
+ devices_upstream_dns: [
+ "cannot be changed; it is overridden by DEVICES_UPSTREAM_DNS environment variable"
]
}
end
- test "trims binary fields" do
- config = Repo.one(Domain.Config.Configuration)
+ test "trims binary fields", %{account: account} do
+ config = get_account_config_by_account_id(account.id)
attrs = %{
- default_client_dns: [" foobar.com", "google.com "],
- default_client_endpoint: " 127.0.0.1 "
+ devices_upstream_dns: [" foobar.com", "google.com "]
}
assert {:ok, config} = update_config(config, attrs)
- assert config.default_client_dns == ["foobar.com", "google.com"]
- assert config.default_client_endpoint == "127.0.0.1"
+ assert config.devices_upstream_dns == ["foobar.com", "google.com"]
end
- test "changes database config value" do
- config = Repo.one(Domain.Config.Configuration)
- attrs = %{default_client_dns: ["foobar.com", "google.com"]}
+ test "changes database config value when it did not exist", %{account: account} do
+ config = get_account_config_by_account_id(account.id)
+ attrs = %{devices_upstream_dns: ["foobar.com", "google.com"]}
assert {:ok, config} = update_config(config, attrs)
- assert config.default_client_dns == attrs.default_client_dns
- end
- end
-
- describe "put_config!/2" do
- test "updates config field in a database" do
- assert config = put_config!(:default_client_endpoint, " 127.0.0.1")
- assert config.default_client_endpoint == "127.0.0.1"
- assert Repo.one(Domain.Config.Configuration).default_client_endpoint == "127.0.0.1"
+ assert config.devices_upstream_dns == attrs.devices_upstream_dns
end
- test "raises when config field is not valid" do
- assert_raise RuntimeError, fn ->
- put_config!(:default_client_endpoint, "!!!")
- end
+ test "changes database config value when it existed", %{account: account} do
+ ConfigFixtures.upsert_configuration(account: account)
+
+ config = get_account_config_by_account_id(account.id)
+ attrs = %{devices_upstream_dns: ["foobar.com", "google.com"]}
+ assert {:ok, config} = update_config(config, attrs)
+ assert config.devices_upstream_dns == attrs.devices_upstream_dns
end
end
end
diff --git a/apps/domain/test/domain/devices_test.exs b/apps/domain/test/domain/devices_test.exs
new file mode 100644
index 000000000..a10ee2592
--- /dev/null
+++ b/apps/domain/test/domain/devices_test.exs
@@ -0,0 +1,675 @@
+defmodule Domain.DevicesTest do
+ use Domain.DataCase, async: true
+ import Domain.Devices
+ alias Domain.AccountsFixtures
+ alias Domain.{NetworkFixtures, ActorsFixtures, AuthFixtures, DevicesFixtures}
+ alias Domain.Devices
+
+ setup do
+ account = AccountsFixtures.create_account()
+
+ unprivileged_actor = ActorsFixtures.create_actor(type: :account_user, account: account)
+
+ unprivileged_identity =
+ AuthFixtures.create_identity(account: account, actor: unprivileged_actor)
+
+ unprivileged_subject = AuthFixtures.create_subject(unprivileged_identity)
+
+ admin_actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
+ admin_identity = AuthFixtures.create_identity(account: account, actor: admin_actor)
+ admin_subject = AuthFixtures.create_subject(admin_identity)
+
+ %{
+ account: account,
+ unprivileged_actor: unprivileged_actor,
+ unprivileged_identity: unprivileged_identity,
+ unprivileged_subject: unprivileged_subject,
+ admin_actor: admin_actor,
+ admin_identity: admin_identity,
+ admin_subject: admin_subject
+ }
+ end
+
+ describe "count_by_account_id/0" do
+ test "counts devices for an account", %{account: account} do
+ DevicesFixtures.create_device(account: account)
+ DevicesFixtures.create_device(account: account)
+ DevicesFixtures.create_device(account: account)
+ DevicesFixtures.create_device()
+
+ assert count_by_account_id(account.id) == 3
+ end
+ end
+
+ describe "count_by_actor_id/1" do
+ test "returns 0 if actor does not exist" do
+ assert count_by_actor_id(Ecto.UUID.generate()) == 0
+ end
+
+ test "returns count of devices for a actor" do
+ device = DevicesFixtures.create_device()
+ assert count_by_actor_id(device.actor_id) == 1
+ end
+ end
+
+ describe "fetch_device_by_id/2" do
+ test "returns error when UUID is invalid", %{unprivileged_subject: subject} do
+ assert fetch_device_by_id("foo", subject) == {:error, :not_found}
+ end
+
+ test "does not return deleted devices", %{
+ unprivileged_actor: actor,
+ unprivileged_subject: subject
+ } do
+ device =
+ DevicesFixtures.create_device(actor: actor)
+ |> DevicesFixtures.delete_device()
+
+ assert fetch_device_by_id(device.id, subject) == {:error, :not_found}
+ end
+
+ test "returns device by id", %{unprivileged_actor: actor, unprivileged_subject: subject} do
+ device = DevicesFixtures.create_device(actor: actor)
+ assert fetch_device_by_id(device.id, subject) == {:ok, device}
+ end
+
+ test "returns device that belongs to another actor with manage permission", %{
+ account: account,
+ unprivileged_subject: subject
+ } do
+ device = DevicesFixtures.create_device(account: account)
+
+ subject =
+ subject
+ |> AuthFixtures.remove_permissions()
+ |> AuthFixtures.add_permission(Devices.Authorizer.manage_devices_permission())
+
+ assert fetch_device_by_id(device.id, subject) == {:ok, device}
+ end
+
+ test "does not returns device that belongs to another account with manage permission", %{
+ unprivileged_subject: subject
+ } do
+ device = DevicesFixtures.create_device()
+
+ subject =
+ subject
+ |> AuthFixtures.remove_permissions()
+ |> AuthFixtures.add_permission(Devices.Authorizer.manage_devices_permission())
+
+ assert fetch_device_by_id(device.id, subject) == {:error, :not_found}
+ end
+
+ test "does not return device that belongs to another actor with manage_own permission", %{
+ unprivileged_subject: subject
+ } do
+ device = DevicesFixtures.create_device()
+
+ subject =
+ subject
+ |> AuthFixtures.remove_permissions()
+ |> AuthFixtures.add_permission(Devices.Authorizer.manage_own_devices_permission())
+
+ assert fetch_device_by_id(device.id, subject) == {:error, :not_found}
+ end
+
+ test "returns error when device does not exist", %{unprivileged_subject: subject} do
+ assert fetch_device_by_id(Ecto.UUID.generate(), subject) ==
+ {:error, :not_found}
+ end
+
+ test "returns error when subject has no permission to view devices", %{
+ unprivileged_subject: subject
+ } do
+ subject = AuthFixtures.remove_permissions(subject)
+
+ assert fetch_device_by_id(Ecto.UUID.generate(), subject) ==
+ {:error,
+ {:unauthorized,
+ [
+ missing_permissions: [
+ {:one_of,
+ [
+ Devices.Authorizer.manage_devices_permission(),
+ Devices.Authorizer.manage_own_devices_permission()
+ ]}
+ ]
+ ]}}
+ end
+ end
+
+ describe "list_devices/1" do
+ test "returns empty list when there are no devices", %{admin_subject: subject} do
+ assert list_devices(subject) == {:ok, []}
+ end
+
+ test "does not list deleted devices", %{
+ unprivileged_actor: actor,
+ unprivileged_subject: subject
+ } do
+ DevicesFixtures.create_device(actor: actor)
+ |> DevicesFixtures.delete_device()
+
+ assert list_devices(subject) == {:ok, []}
+ end
+
+ test "does not list devices in other accounts", %{
+ unprivileged_subject: subject
+ } do
+ DevicesFixtures.create_device()
+
+ assert list_devices(subject) == {:ok, []}
+ end
+
+ test "shows all devices owned by a actor for unprivileged subject", %{
+ unprivileged_actor: actor,
+ admin_actor: other_actor,
+ unprivileged_subject: subject
+ } do
+ device = DevicesFixtures.create_device(actor: actor)
+ DevicesFixtures.create_device(actor: other_actor)
+
+ assert list_devices(subject) == {:ok, [device]}
+ end
+
+ test "shows all devices for admin subject", %{
+ unprivileged_actor: other_actor,
+ admin_actor: admin_actor,
+ admin_subject: subject
+ } do
+ DevicesFixtures.create_device(actor: admin_actor)
+ DevicesFixtures.create_device(actor: other_actor)
+
+ assert {:ok, devices} = list_devices(subject)
+ assert length(devices) == 2
+ end
+
+ test "returns error when subject has no permission to manage devices", %{
+ unprivileged_subject: subject
+ } do
+ subject = AuthFixtures.remove_permissions(subject)
+
+ assert list_devices(subject) ==
+ {:error,
+ {:unauthorized,
+ [
+ missing_permissions: [
+ {:one_of,
+ [
+ Devices.Authorizer.manage_devices_permission(),
+ Devices.Authorizer.manage_own_devices_permission()
+ ]}
+ ]
+ ]}}
+ end
+ end
+
+ describe "list_devices_by_actor_id/2" do
+ test "returns empty list when there are no devices for a given actor", %{
+ admin_actor: actor,
+ admin_subject: subject
+ } do
+ assert list_devices_by_actor_id(Ecto.UUID.generate(), subject) == {:ok, []}
+ assert list_devices_by_actor_id(actor.id, subject) == {:ok, []}
+ DevicesFixtures.create_device()
+ assert list_devices_by_actor_id(actor.id, subject) == {:ok, []}
+ end
+
+ test "returns error when actor id is invalid", %{admin_subject: subject} do
+ assert list_devices_by_actor_id("foo", subject) == {:error, :not_found}
+ end
+
+ test "does not list deleted devices", %{
+ unprivileged_actor: actor,
+ unprivileged_identity: identity,
+ unprivileged_subject: subject
+ } do
+ DevicesFixtures.create_device(identity: identity)
+ |> DevicesFixtures.delete_device()
+
+ assert list_devices_by_actor_id(actor.id, subject) == {:ok, []}
+ end
+
+ test "does not deleted devices for actors in other accounts", %{
+ unprivileged_subject: unprivileged_subject,
+ admin_subject: admin_subject
+ } do
+ actor = ActorsFixtures.create_actor(type: :account_user)
+ DevicesFixtures.create_device(actor: actor)
+
+ assert list_devices_by_actor_id(actor.id, unprivileged_subject) == {:ok, []}
+ assert list_devices_by_actor_id(actor.id, admin_subject) == {:ok, []}
+ end
+
+ test "shows only devices owned by a actor for unprivileged subject", %{
+ unprivileged_actor: actor,
+ admin_actor: other_actor,
+ unprivileged_subject: subject
+ } do
+ device = DevicesFixtures.create_device(actor: actor)
+ DevicesFixtures.create_device(actor: other_actor)
+
+ assert list_devices_by_actor_id(actor.id, subject) == {:ok, [device]}
+ assert list_devices_by_actor_id(other_actor.id, subject) == {:ok, []}
+ end
+
+ test "shows all devices owned by another actor for admin subject", %{
+ unprivileged_actor: other_actor,
+ admin_actor: admin_actor,
+ admin_subject: subject
+ } do
+ DevicesFixtures.create_device(actor: admin_actor)
+ DevicesFixtures.create_device(actor: other_actor)
+
+ assert {:ok, [_device]} = list_devices_by_actor_id(admin_actor.id, subject)
+ assert {:ok, [_device]} = list_devices_by_actor_id(other_actor.id, subject)
+ end
+
+ test "returns error when subject has no permission to manage devices", %{
+ unprivileged_subject: subject
+ } do
+ subject = AuthFixtures.remove_permissions(subject)
+
+ assert list_devices_by_actor_id(Ecto.UUID.generate(), subject) ==
+ {:error,
+ {:unauthorized,
+ [
+ missing_permissions: [
+ {:one_of,
+ [
+ Devices.Authorizer.manage_devices_permission(),
+ Devices.Authorizer.manage_own_devices_permission()
+ ]}
+ ]
+ ]}}
+ end
+ end
+
+ describe "change_device/1" do
+ test "returns changeset with given changes", %{admin_actor: actor} do
+ device = DevicesFixtures.create_device(actor: actor)
+ device_attrs = DevicesFixtures.device_attrs()
+
+ assert changeset = change_device(device, device_attrs)
+ assert %Ecto.Changeset{data: %Domain.Devices.Device{}} = changeset
+
+ assert changeset.changes == %{name: device_attrs.name}
+ end
+ end
+
+ describe "upsert_device/2" do
+ test "returns errors on invalid attrs", %{
+ admin_subject: subject
+ } do
+ attrs = %{
+ external_id: nil,
+ public_key: "x",
+ ipv4: "1.1.1.256",
+ ipv6: "fd01::10000"
+ }
+
+ assert {:error, changeset} = upsert_device(attrs, subject)
+
+ assert errors_on(changeset) == %{
+ public_key: ["should be 44 character(s)", "must be a base64-encoded string"],
+ external_id: ["can't be blank"]
+ }
+ end
+
+ test "allows creating device with just required attributes", %{
+ admin_actor: actor,
+ admin_identity: identity,
+ admin_subject: subject
+ } do
+ attrs =
+ DevicesFixtures.device_attrs()
+ |> Map.delete(:name)
+
+ assert {:ok, device} = upsert_device(attrs, subject)
+
+ assert device.name
+
+ assert device.public_key == attrs.public_key
+
+ assert device.actor_id == actor.id
+ assert device.identity_id == identity.id
+ assert device.account_id == actor.account_id
+
+ refute is_nil(device.ipv4)
+ refute is_nil(device.ipv6)
+
+ assert device.last_seen_remote_ip == %Postgrex.INET{address: subject.context.remote_ip}
+ assert device.last_seen_user_agent == subject.context.user_agent
+ assert device.last_seen_version == "0.7.412"
+ assert device.last_seen_at
+ end
+
+ test "updates device when it already exists", %{
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device(subject: subject)
+ attrs = DevicesFixtures.device_attrs(external_id: device.external_id)
+
+ subject = %{
+ subject
+ | context: %Domain.Auth.Context{
+ subject.context
+ | remote_ip: {100, 64, 100, 101},
+ user_agent: "iOS/12.5 (iPhone) connlib/0.7.411"
+ }
+ }
+
+ assert {:ok, updated_device} = upsert_device(attrs, subject)
+
+ assert Repo.aggregate(Devices.Device, :count, :id) == 1
+
+ assert updated_device.name
+ assert updated_device.last_seen_remote_ip.address == subject.context.remote_ip
+ assert updated_device.last_seen_remote_ip != device.last_seen_remote_ip
+ assert updated_device.last_seen_user_agent == subject.context.user_agent
+ assert updated_device.last_seen_user_agent != device.last_seen_user_agent
+ assert updated_device.last_seen_version == "0.7.411"
+ assert updated_device.public_key != device.public_key
+ assert updated_device.public_key == attrs.public_key
+
+ assert updated_device.actor_id == device.actor_id
+ assert updated_device.identity_id == device.identity_id
+ assert updated_device.ipv4 == device.ipv4
+ assert updated_device.ipv6 == device.ipv6
+ assert updated_device.last_seen_at
+ assert updated_device.last_seen_at != device.last_seen_at
+ end
+
+ test "does not reserve additional addresses on update", %{
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device(subject: subject)
+
+ attrs =
+ DevicesFixtures.device_attrs(
+ external_id: device.external_id,
+ last_seen_user_agent: "iOS/12.5 (iPhone) connlib/0.7.411",
+ last_seen_remote_ip: %Postgrex.INET{address: {100, 64, 100, 100}}
+ )
+
+ assert {:ok, updated_device} = upsert_device(attrs, subject)
+
+ addresses =
+ Domain.Network.Address
+ |> Repo.all()
+ |> Enum.map(fn %Domain.Network.Address{address: address, type: type} ->
+ %{address: address, type: type}
+ end)
+
+ assert length(addresses) == 2
+ assert %{address: updated_device.ipv4, type: :ipv4} in addresses
+ assert %{address: updated_device.ipv6, type: :ipv6} in addresses
+ end
+
+ test "allows unprivileged actor to create a device for himself", %{
+ admin_subject: subject
+ } do
+ attrs =
+ DevicesFixtures.device_attrs()
+ |> Map.delete(:name)
+
+ assert {:ok, _device} = upsert_device(attrs, subject)
+ end
+
+ test "does not allow to reuse IP addresses", %{
+ account: account,
+ admin_subject: subject
+ } do
+ attrs = DevicesFixtures.device_attrs(account: account)
+ assert {:ok, device} = upsert_device(attrs, subject)
+
+ addresses =
+ Domain.Network.Address
+ |> Repo.all()
+ |> Enum.map(fn %Domain.Network.Address{address: address, type: type} ->
+ %{address: address, type: type}
+ end)
+
+ assert length(addresses) == 2
+ assert %{address: device.ipv4, type: :ipv4} in addresses
+ assert %{address: device.ipv6, type: :ipv6} in addresses
+
+ assert_raise Ecto.ConstraintError, fn ->
+ NetworkFixtures.create_address(address: device.ipv4, account: account)
+ end
+
+ assert_raise Ecto.ConstraintError, fn ->
+ NetworkFixtures.create_address(address: device.ipv6, account: account)
+ end
+ end
+
+ test "ip addresses are unique per account", %{
+ account: account,
+ admin_subject: subject
+ } do
+ attrs = DevicesFixtures.device_attrs(account: account)
+ assert {:ok, device} = upsert_device(attrs, subject)
+
+ assert %Domain.Network.Address{} = NetworkFixtures.create_address(address: device.ipv4)
+ assert %Domain.Network.Address{} = NetworkFixtures.create_address(address: device.ipv6)
+ end
+
+ test "returns error when subject has no permission to create devices", %{
+ admin_subject: subject
+ } do
+ subject = AuthFixtures.remove_permissions(subject)
+
+ assert upsert_device(%{}, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Devices.Authorizer.manage_own_devices_permission()]]}}
+ end
+ end
+
+ describe "update_device/3" do
+ test "allows admin actor to update own devices", %{admin_actor: actor, admin_subject: subject} do
+ device = DevicesFixtures.create_device(actor: actor)
+ attrs = %{name: "new name"}
+
+ assert {:ok, device} = update_device(device, attrs, subject)
+
+ assert device.name == attrs.name
+ end
+
+ test "allows admin actor to update other actors devices", %{
+ account: account,
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device(account: account)
+ attrs = %{name: "new name"}
+
+ assert {:ok, device} = update_device(device, attrs, subject)
+
+ assert device.name == attrs.name
+ end
+
+ test "allows unprivileged actor to update own devices", %{
+ unprivileged_actor: actor,
+ unprivileged_subject: subject
+ } do
+ device = DevicesFixtures.create_device(actor: actor)
+ attrs = %{name: "new name"}
+
+ assert {:ok, device} = update_device(device, attrs, subject)
+
+ assert device.name == attrs.name
+ end
+
+ test "does not allow unprivileged actor to update other actors devices", %{
+ account: account,
+ unprivileged_subject: subject
+ } do
+ device = DevicesFixtures.create_device(account: account)
+ attrs = %{name: "new name"}
+
+ assert update_device(device, attrs, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
+ end
+
+ test "does not allow admin actor to update devices in other accounts", %{
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device()
+ attrs = %{name: "new name"}
+
+ assert update_device(device, attrs, subject) == {:error, :not_found}
+ end
+
+ test "does not allow to reset required fields to empty values", %{
+ admin_actor: actor,
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device(actor: actor)
+ attrs = %{name: nil, public_key: nil}
+
+ assert {:error, changeset} = update_device(device, attrs, subject)
+
+ assert errors_on(changeset) == %{name: ["can't be blank"]}
+ end
+
+ test "returns error on invalid attrs", %{admin_actor: actor, admin_subject: subject} do
+ device = DevicesFixtures.create_device(actor: actor)
+
+ attrs = %{
+ name: String.duplicate("a", 256)
+ }
+
+ assert {:error, changeset} = update_device(device, attrs, subject)
+
+ assert errors_on(changeset) == %{
+ name: ["should be at most 255 character(s)"]
+ }
+ end
+
+ test "ignores updates for any field except name", %{
+ admin_actor: actor,
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device(actor: actor)
+
+ fields = Devices.Device.__schema__(:fields) -- [:name]
+ value = -1
+
+ for field <- fields do
+ assert {:ok, updated_device} = update_device(device, %{field => value}, subject)
+ assert updated_device == device
+ end
+ end
+
+ test "returns error when subject has no permission to update devices", %{
+ admin_actor: actor,
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device(actor: actor)
+
+ subject = AuthFixtures.remove_permissions(subject)
+
+ assert update_device(device, %{}, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Devices.Authorizer.manage_own_devices_permission()]]}}
+
+ device = DevicesFixtures.create_device()
+
+ assert update_device(device, %{}, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
+ end
+ end
+
+ describe "delete_device/2" do
+ test "returns error on state conflict", %{admin_actor: actor, admin_subject: subject} do
+ device = DevicesFixtures.create_device(actor: actor)
+
+ assert {:ok, deleted} = delete_device(device, subject)
+ assert delete_device(deleted, subject) == {:error, :not_found}
+ assert delete_device(device, subject) == {:error, :not_found}
+ end
+
+ test "admin can delete own devices", %{admin_actor: actor, admin_subject: subject} do
+ device = DevicesFixtures.create_device(actor: actor)
+
+ assert {:ok, deleted} = delete_device(device, subject)
+ assert deleted.deleted_at
+ end
+
+ test "admin can delete other people devices", %{
+ unprivileged_actor: actor,
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device(actor: actor)
+
+ assert {:ok, deleted} = delete_device(device, subject)
+ assert deleted.deleted_at
+ end
+
+ test "admin can not delete devices in other accounts", %{
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device()
+
+ assert delete_device(device, subject) == {:error, :not_found}
+ end
+
+ test "unprivileged can delete own devices", %{
+ account: account,
+ unprivileged_actor: actor,
+ unprivileged_subject: subject
+ } do
+ device = DevicesFixtures.create_device(account: account, actor: actor)
+
+ assert {:ok, deleted} = delete_device(device, subject)
+ assert deleted.deleted_at
+ end
+
+ test "unprivileged can not delete other people devices", %{
+ account: account,
+ unprivileged_subject: subject
+ } do
+ device = DevicesFixtures.create_device()
+
+ assert delete_device(device, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
+
+ device = DevicesFixtures.create_device(account: account)
+
+ assert delete_device(device, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
+
+ assert Repo.aggregate(Devices.Device, :count) == 2
+ end
+
+ test "returns error when subject has no permission to delete devices", %{
+ admin_actor: actor,
+ admin_subject: subject
+ } do
+ device = DevicesFixtures.create_device(actor: actor)
+
+ subject = AuthFixtures.remove_permissions(subject)
+
+ assert delete_device(device, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Devices.Authorizer.manage_own_devices_permission()]]}}
+
+ device = DevicesFixtures.create_device()
+
+ assert delete_device(device, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Devices.Authorizer.manage_devices_permission()]]}}
+ end
+ end
+end
diff --git a/apps/domain/test/domain/gateways_test.exs b/apps/domain/test/domain/gateways_test.exs
index 1c631d948..a85749e35 100644
--- a/apps/domain/test/domain/gateways_test.exs
+++ b/apps/domain/test/domain/gateways_test.exs
@@ -7,7 +7,7 @@ defmodule Domain.GatewaysTest do
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -721,4 +721,32 @@ defmodule Domain.GatewaysTest do
[missing_permissions: [Gateways.Authorizer.manage_gateways_permission()]]}}
end
end
+
+ describe "encode_token!/1" do
+ test "returns encoded token" do
+ token = GatewaysFixtures.create_token()
+ assert encrypted_secret = encode_token!(token)
+
+ config = Application.fetch_env!(:domain, Domain.Gateways)
+ key_base = Keyword.fetch!(config, :key_base)
+ salt = Keyword.fetch!(config, :salt)
+
+ assert Plug.Crypto.verify(key_base, salt, encrypted_secret) ==
+ {:ok, {token.id, token.value}}
+ end
+ end
+
+ describe "authorize_gateway/1" do
+ test "returns token when encoded secret is valid" do
+ token = GatewaysFixtures.create_token()
+ encoded_token = encode_token!(token)
+ assert {:ok, fetched_token} = authorize_gateway(encoded_token)
+ assert fetched_token.id == token.id
+ assert is_nil(fetched_token.value)
+ end
+
+ test "returns error when secret is invalid" do
+ assert authorize_gateway(Ecto.UUID.generate()) == {:error, :invalid_token}
+ end
+ end
end
diff --git a/apps/domain/test/domain/relays_test.exs b/apps/domain/test/domain/relays_test.exs
index d475dd07e..edcbb1e29 100644
--- a/apps/domain/test/domain/relays_test.exs
+++ b/apps/domain/test/domain/relays_test.exs
@@ -7,7 +7,7 @@ defmodule Domain.RelaysTest do
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
@@ -599,4 +599,32 @@ defmodule Domain.RelaysTest do
[missing_permissions: [Relays.Authorizer.manage_relays_permission()]]}}
end
end
+
+ describe "encode_token!/1" do
+ test "returns encoded token" do
+ token = RelaysFixtures.create_token()
+ assert encrypted_secret = encode_token!(token)
+
+ config = Application.fetch_env!(:domain, Domain.Relays)
+ key_base = Keyword.fetch!(config, :key_base)
+ salt = Keyword.fetch!(config, :salt)
+
+ assert Plug.Crypto.verify(key_base, salt, encrypted_secret) ==
+ {:ok, {token.id, token.value}}
+ end
+ end
+
+ describe "authorize_relay/1" do
+ test "returns token when encoded secret is valid" do
+ token = RelaysFixtures.create_token()
+ encoded_token = encode_token!(token)
+ assert {:ok, fetched_token} = authorize_relay(encoded_token)
+ assert fetched_token.id == token.id
+ assert is_nil(fetched_token.value)
+ end
+
+ test "returns error when secret is invalid" do
+ assert authorize_relay(Ecto.UUID.generate()) == {:error, :invalid_token}
+ end
+ end
end
diff --git a/apps/domain/test/domain/release_test.exs b/apps/domain/test/domain/release_test.exs
deleted file mode 100644
index fa8db9d19..000000000
--- a/apps/domain/test/domain/release_test.exs
+++ /dev/null
@@ -1,55 +0,0 @@
-# defmodule Domain.ReleaseTest do
-# @moduledoc """
-# XXX: Write more meaningful tests for this module.
-# Perhaps the best way to test this module is through functional tests.
-# """
-# use Domain.DataCase, async: true
-# alias Domain.{ApiTokens, Users}
-# alias Domain.Release
-# alias Domain.UsersFixtures
-
-# describe "migrate/0" do
-# test "function runs without error" do
-# assert Release.migrate()
-# end
-# end
-
-# describe "create_admin_user/0" do
-# test "creates admin when none exists" do
-# Release.create_admin_user()
-# email = Domain.Config.fetch_env!(:domain, :admin_email)
-# assert {:ok, %Users.User{}} = Users.fetch_user_by_email(email)
-# end
-
-# test "reset admin password when user exists" do
-# {:ok, first_user} = Release.create_admin_user()
-# {:ok, new_first_user} = Release.change_password(first_user.email, "newpassword1234")
-# {:ok, second_user} = Release.create_admin_user()
-
-# assert second_user.password_hash != new_first_user.password_hash
-# end
-# end
-
-# describe "create_api_token/1" do
-# test "creates api_token_token for default admin user" do
-# admin_user =
-# UsersFixtures.create_user_with_role(:admin, %{
-# email: Domain.Config.fetch_env!(:domain, :admin_email)
-# })
-
-# assert :ok = Release.create_api_token()
-# assert ApiTokens.count_by_user_id(admin_user.id) == 1
-# end
-# end
-
-# describe "change_password/2" do
-# test "changes password" do
-# user = UsersFixtures.create_user_with_role(:unprivileged)
-
-# Release.change_password(user.email, "this password should be different")
-# assert {:ok, new_user} = Users.fetch_user_by_email(user.email)
-
-# assert new_user.password_hash != user.password_hash
-# end
-# end
-# end
diff --git a/apps/domain/test/domain/resources_test.exs b/apps/domain/test/domain/resources_test.exs
index b0ea1045a..61f75fb19 100644
--- a/apps/domain/test/domain/resources_test.exs
+++ b/apps/domain/test/domain/resources_test.exs
@@ -7,7 +7,7 @@ defmodule Domain.ResourcesTest do
setup do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
subject = AuthFixtures.create_subject(identity)
diff --git a/apps/domain/test/domain/telemetry_test.exs b/apps/domain/test/domain/telemetry_test.exs
index d038caadf..866cea0cf 100644
--- a/apps/domain/test/domain/telemetry_test.exs
+++ b/apps/domain/test/domain/telemetry_test.exs
@@ -1,150 +1,150 @@
-defmodule Domain.TelemetryTest do
- use Domain.DataCase, async: true
- # import Domain.TestHelpers
- # alias Domain.Telemetry
- # alias Domain.MFAFixtures
+# defmodule Domain.TelemetryTest do
+# use Domain.DataCase, async: true
+# # import Domain.TestHelpers
+# # alias Domain.Telemetry
+# # alias Domain.MFAFixtures
- # describe "user" do
- # setup :create_user
+# # describe "user" do
+# # setup :create_user
- # test "count" do
- # ping_data = Telemetry.ping_data()
+# # test "count" do
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:user_count] == 1
- # end
+# # assert ping_data[:user_count] == 1
+# # end
- # test "count mfa", %{user: user} do
- # {:ok, [user: other_user]} = create_user(%{})
- # MFAFixtures.create_totp_method(user: user)
- # MFAFixtures.create_totp_method(user: other_user)
- # ping_data = Telemetry.ping_data()
+# # test "count mfa", %{user: user} do
+# # {:ok, [user: other_user]} = create_user(%{})
+# # MFAFixtures.create_totp_method(user: user)
+# # MFAFixtures.create_totp_method(user: other_user)
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:users_with_mfa] == 2
- # assert ping_data[:users_with_mfa_totp] == 2
- # end
- # end
+# # assert ping_data[:users_with_mfa] == 2
+# # assert ping_data[:users_with_mfa_totp] == 2
+# # end
+# # end
- # describe "device" do
- # setup [:create_devices, :create_other_user_device]
+# # describe "device" do
+# # setup [:create_devices, :create_other_user_device]
- # test "count" do
- # ping_data = Telemetry.ping_data()
+# # test "count" do
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:device_count] == 6
- # end
+# # assert ping_data[:device_count] == 6
+# # end
- # test "max count for users" do
- # ping_data = Telemetry.ping_data()
+# # test "max count for users" do
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:max_devices_for_users] == 5
- # end
- # end
+# # assert ping_data[:max_devices_for_users] == 5
+# # end
+# # end
- # describe "auth" do
- # test "count openid providers" do
- # Domain.ConfigFixtures.start_openid_providers([
- # "google",
- # "okta",
- # "auth0",
- # "azure",
- # "onelogin",
- # "keycloak",
- # "vault"
- # ])
+# # describe "auth" do
+# # test "count openid providers" do
+# # Domain.ConfigFixtures.start_openid_providers([
+# # "google",
+# # "okta",
+# # "auth0",
+# # "azure",
+# # "onelogin",
+# # "keycloak",
+# # "vault"
+# # ])
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:openid_providers] == 7
- # end
+# # assert ping_data[:openid_providers] == 7
+# # end
- # test "disable vpn on oidc error enabled" do
- # Domain.Config.put_config!(:disable_vpn_on_oidc_error, true)
+# # test "disable vpn on oidc error enabled" do
+# # Domain.Config.put_config!(:disable_vpn_on_oidc_error, true)
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:disable_vpn_on_oidc_error]
- # end
+# # assert ping_data[:disable_vpn_on_oidc_error]
+# # end
- # test "disable vpn on oidc error disabled" do
- # Domain.Config.put_config!(:disable_vpn_on_oidc_error, false)
+# # test "disable vpn on oidc error disabled" do
+# # Domain.Config.put_config!(:disable_vpn_on_oidc_error, false)
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # refute ping_data[:disable_vpn_on_oidc_error]
- # end
+# # refute ping_data[:disable_vpn_on_oidc_error]
+# # end
- # test "local authentication enabled" do
- # Domain.Config.put_config!(:local_auth_enabled, true)
+# # test "local authentication enabled" do
+# # Domain.Config.put_config!(:local_auth_enabled, true)
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:local_authentication]
- # end
+# # assert ping_data[:local_authentication]
+# # end
- # test "local authentication disabled" do
- # Domain.Config.put_config!(:local_auth_enabled, false)
+# # test "local authentication disabled" do
+# # Domain.Config.put_config!(:local_auth_enabled, false)
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # refute ping_data[:local_authentication]
- # end
+# # refute ping_data[:local_authentication]
+# # end
- # test "unprivileged device management enabled" do
- # Domain.Config.put_config!(:allow_unprivileged_device_management, true)
+# # test "unprivileged device management enabled" do
+# # Domain.Config.put_config!(:allow_unprivileged_device_management, true)
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:unprivileged_device_management]
- # end
+# # assert ping_data[:unprivileged_device_management]
+# # end
- # test "unprivileged device configuration enabled" do
- # Domain.Config.put_config!(:allow_unprivileged_device_configuration, true)
+# # test "unprivileged device configuration enabled" do
+# # Domain.Config.put_config!(:allow_unprivileged_device_configuration, true)
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:unprivileged_device_configuration]
- # end
+# # assert ping_data[:unprivileged_device_configuration]
+# # end
- # test "unprivileged device configuration disabled" do
- # Domain.Config.put_config!(:allow_unprivileged_device_configuration, false)
+# # test "unprivileged device configuration disabled" do
+# # Domain.Config.put_config!(:allow_unprivileged_device_configuration, false)
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # refute ping_data[:unprivileged_device_configuration]
- # end
- # end
+# # refute ping_data[:unprivileged_device_configuration]
+# # end
+# # end
- # describe "database" do
- # test "local hostname" do
- # Domain.Config.put_env_override(:domain, Domain.Repo, hostname: "localhost")
+# # describe "database" do
+# # test "local hostname" do
+# # Domain.Config.put_env_override(:domain, Domain.Repo, hostname: "localhost")
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # refute ping_data[:external_database]
- # end
+# # refute ping_data[:external_database]
+# # end
- # test "local url" do
- # Domain.Config.put_env_override(:domain, Domain.Repo, url: "postgres://127.0.0.1")
+# # test "local url" do
+# # Domain.Config.put_env_override(:domain, Domain.Repo, url: "postgres://127.0.0.1")
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # refute ping_data[:external_database]
- # end
+# # refute ping_data[:external_database]
+# # end
- # test "external hostname" do
- # Domain.Config.put_env_override(:domain, Domain.Repo, hostname: "firezone.dev")
+# # test "external hostname" do
+# # Domain.Config.put_env_override(:domain, Domain.Repo, hostname: "firezone.dev")
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:external_database]
- # end
+# # assert ping_data[:external_database]
+# # end
- # test "external url" do
- # Domain.Config.put_env_override(:domain, Domain.Repo, url: "postgres://firezone.dev")
+# # test "external url" do
+# # Domain.Config.put_env_override(:domain, Domain.Repo, url: "postgres://firezone.dev")
- # ping_data = Telemetry.ping_data()
+# # ping_data = Telemetry.ping_data()
- # assert ping_data[:external_database]
- # end
- # end
-end
+# # assert ping_data[:external_database]
+# # end
+# # end
+# end
diff --git a/apps/domain/test/support/fixtures/actors_fixtures.ex b/apps/domain/test/support/fixtures/actors_fixtures.ex
index c9ac1317d..148be5946 100644
--- a/apps/domain/test/support/fixtures/actors_fixtures.ex
+++ b/apps/domain/test/support/fixtures/actors_fixtures.ex
@@ -5,8 +5,7 @@ defmodule Domain.ActorsFixtures do
def actor_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
- type: :user,
- role: :unprivileged
+ type: :account_user
})
end
diff --git a/apps/domain/test/support/fixtures/api_tokens_fixtures.ex b/apps/domain/test/support/fixtures/api_tokens_fixtures.ex
deleted file mode 100644
index c2355c86a..000000000
--- a/apps/domain/test/support/fixtures/api_tokens_fixtures.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-defmodule Domain.ApiTokensFixtures do
- alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures}
-
- def api_token_attrs(attrs \\ %{}) do
- Enum.into(attrs, %{})
- end
-
- def create_api_token(attrs \\ %{}) do
- attrs = api_token_attrs(attrs)
-
- {account, attrs} =
- Map.pop_lazy(attrs, :account, fn ->
- AccountsFixtures.create_account()
- end)
-
- {subject, attrs} =
- Map.pop_lazy(attrs, :subject, fn ->
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
- identity = AuthFixtures.create_identity(account: account, actor: actor)
- AuthFixtures.create_subject(identity)
- end)
-
- {:ok, api_token} = Domain.ApiTokens.create_api_token(attrs, subject)
- api_token
- end
-
- def expire_api_token(api_token) do
- one_second_ago = DateTime.utc_now() |> DateTime.add(-1, :second)
-
- Ecto.Changeset.change(api_token, expires_at: one_second_ago)
- |> Domain.Repo.update!()
- end
-end
diff --git a/apps/domain/test/support/fixtures/auth_fixtures.ex b/apps/domain/test/support/fixtures/auth_fixtures.ex
index c702da836..83adf4a7a 100644
--- a/apps/domain/test/support/fixtures/auth_fixtures.ex
+++ b/apps/domain/test/support/fixtures/auth_fixtures.ex
@@ -15,6 +15,10 @@ defmodule Domain.AuthFixtures do
Ecto.UUID.generate()
end
+ def random_provider_identifier(%Domain.Auth.Provider{adapter: :token}) do
+ Ecto.UUID.generate()
+ end
+
def random_provider_identifier(%Domain.Auth.Provider{adapter: :userpass, name: name}) do
"user-#{counter()}@#{String.downcase(name)}.com"
end
@@ -72,6 +76,20 @@ defmodule Domain.AuthFixtures do
provider
end
+ def create_token_provider(attrs \\ %{}) do
+ attrs = Enum.into(attrs, %{})
+
+ {account, _attrs} =
+ Map.pop_lazy(attrs, :account, fn ->
+ AccountsFixtures.create_account()
+ end)
+
+ attrs = provider_attrs(adapter: :token)
+
+ {:ok, provider} = Auth.create_provider(account, attrs)
+ provider
+ end
+
def create_identity(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
@@ -118,7 +136,7 @@ defmodule Domain.AuthFixtures do
def create_subject do
account = AccountsFixtures.create_account()
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = create_identity(actor: actor, account: account)
create_subject(identity)
end
@@ -129,7 +147,7 @@ defmodule Domain.AuthFixtures do
%Auth.Subject{
identity: identity,
actor: identity.actor,
- permissions: Auth.Roles.build(identity.actor.role).permissions,
+ permissions: Auth.Roles.build(identity.actor.type).permissions,
account: identity.account,
expires_at: DateTime.utc_now() |> DateTime.add(60, :second),
context: %Auth.Context{remote_ip: remote_ip(), user_agent: user_agent()}
@@ -148,6 +166,292 @@ defmodule Domain.AuthFixtures do
%{subject | permissions: MapSet.put(subject.permissions, permission)}
end
+ def start_openid_providers(provider_names, overrides \\ %{}) do
+ {bypass, discovery_document_url} = discovery_document_server()
+
+ openid_connect_providers_attrs =
+ discovery_document_url
+ |> openid_connect_providers_attrs()
+ |> Enum.filter(&(&1["id"] in provider_names))
+ |> Enum.map(fn config ->
+ config
+ |> Enum.into(%{})
+ |> Map.merge(overrides)
+ end)
+
+ {bypass, openid_connect_providers_attrs}
+ end
+
+ def openid_connect_provider_attrs(overrides \\ %{}) do
+ Enum.into(overrides, %{
+ "id" => "google",
+ "discovery_document_uri" => "https://firezone.example.com/.well-known/openid-configuration",
+ "client_id" => "google-client-id",
+ "client_secret" => "google-client-secret",
+ "redirect_uri" => "https://firezone.example.com/auth/oidc/google/callback/",
+ "response_type" => "code",
+ "scope" => "openid email profile",
+ "label" => "OIDC Google",
+ "auto_create_users" => false
+ })
+ end
+
+ defp openid_connect_providers_attrs(discovery_document_url) do
+ [
+ %{
+ "id" => "google",
+ "discovery_document_uri" => discovery_document_url,
+ "client_id" => "google-client-id",
+ "client_secret" => "google-client-secret",
+ "redirect_uri" => "https://firezone.example.com/auth/oidc/google/callback/",
+ "response_type" => "code",
+ "scope" => "openid email profile",
+ "label" => "OIDC Google",
+ "auto_create_users" => false
+ },
+ %{
+ "id" => "okta",
+ "discovery_document_uri" => discovery_document_url,
+ "client_id" => "okta-client-id",
+ "client_secret" => "okta-client-secret",
+ "redirect_uri" => "https://firezone.example.com/auth/oidc/okta/callback/",
+ "response_type" => "code",
+ "scope" => "openid email profile offline_access",
+ "label" => "OIDC Okta",
+ "auto_create_users" => false
+ },
+ %{
+ "id" => "auth0",
+ "discovery_document_uri" => discovery_document_url,
+ "client_id" => "auth0-client-id",
+ "client_secret" => "auth0-client-secret",
+ "redirect_uri" => "https://firezone.example.com/auth/oidc/auth0/callback/",
+ "response_type" => "code",
+ "scope" => "openid email profile",
+ "label" => "OIDC Auth0",
+ "auto_create_users" => false
+ },
+ %{
+ "id" => "azure",
+ "discovery_document_uri" => discovery_document_url,
+ "client_id" => "azure-client-id",
+ "client_secret" => "azure-client-secret",
+ "redirect_uri" => "https://firezone.example.com/auth/oidc/azure/callback/",
+ "response_type" => "code",
+ "scope" => "openid email profile offline_access",
+ "label" => "OIDC Azure",
+ "auto_create_users" => false
+ },
+ %{
+ "id" => "onelogin",
+ "discovery_document_uri" => discovery_document_url,
+ "client_id" => "onelogin-client-id",
+ "client_secret" => "onelogin-client-secret",
+ "redirect_uri" => "https://firezone.example.com/auth/oidc/onelogin/callback/",
+ "response_type" => "code",
+ "scope" => "openid email profile offline_access",
+ "label" => "OIDC Onelogin",
+ "auto_create_users" => false
+ },
+ %{
+ "id" => "keycloak",
+ "discovery_document_uri" => discovery_document_url,
+ "client_id" => "keycloak-client-id",
+ "client_secret" => "keycloak-client-secret",
+ "redirect_uri" => "https://firezone.example.com/auth/oidc/keycloak/callback/",
+ "response_type" => "code",
+ "scope" => "openid email profile offline_access",
+ "label" => "OIDC Keycloak",
+ "auto_create_users" => false
+ },
+ %{
+ "id" => "vault",
+ "discovery_document_uri" => discovery_document_url,
+ "client_id" => "vault-client-id",
+ "client_secret" => "vault-client-secret",
+ "redirect_uri" => "https://firezone.example.com/auth/oidc/vault/callback/",
+ "response_type" => "code",
+ "scope" => "openid email profile offline_access",
+ "label" => "OIDC Vault",
+ "auto_create_users" => false
+ }
+ ]
+ end
+
+ def jwks_attrs do
+ %{
+ "alg" => "RS256",
+ "d" =>
+ "X8TM24Zqbiha9geYYk_vZpANu16IadJLJLJ7ucTc3JaMbK8NCYNcHMoXKnNYPFxmq-UWAEIwh-2" <>
+ "txOiOxuChVrblpfyE4SBJio1T0AUcCwmm8U6G-CsSHMMzWTt2dMTnArHjdyAIgOVRW5SVzhTT" <>
+ "taf4JY-47S-fbcJ7g0hmBbVih5i1sE2fad4I4qFHT-YFU_pnUHbteR6GQuRW4r03Eon8Aje6a" <>
+ "l2AxcYnfF8_cSOIOpkDgGavTtGYhhZPi2jZ7kPm6QGkNW5CyfEq5PGB6JOihw-XIFiiMzYgx0" <>
+ "52rnzoqALoLheXrI0By4kgHSmcqOOmq7aiOff45rlSbpsR",
+ "e" => "AQAB",
+ "kid" => "example@firezone.dev",
+ "kty" => "RSA",
+ "n" =>
+ "qlKll8no4lPYXNSuTTnacpFHiXwPOv_htCYvIXmiR7CWhiiOHQqj7KWXIW7TGxyoLVIyeRM4mwv" <>
+ "kLI-UgsSMYdEKTT0j7Ydjrr0zCunPu5Gxr2yOmcRaszAzGxJL5DwpA0V40RqMlm5OuwdqS4To" <>
+ "_p9LlLxzMF6RZe1OqslV5RZ4Y8FmrWq6BV98eIziEHL0IKdsAIrrOYkkcLDdQeMNuTp_yNB8X" <>
+ "l2TdWSdsbRomrs2dCtCqZcXTsy2EXDceHvYhgAB33nh_w17WLrZQwMM-7kJk36Kk54jZd7i80" <>
+ "AJf_s_plXn1mEh-L5IAL1vg3a9EOMFUl-lPiGqc3td_ykH",
+ "use" => "sig"
+ }
+ end
+
+ def expect_refresh_token(bypass, attrs \\ %{}) do
+ test_pid = self()
+
+ Bypass.expect(bypass, "POST", "/oauth/token", fn conn ->
+ conn = fetch_conn_params(conn)
+ send(test_pid, {:request, conn})
+ Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
+ end)
+ end
+
+ def expect_refresh_token_failure(bypass, attrs \\ %{}) do
+ test_pid = self()
+
+ Bypass.expect(bypass, "POST", "/oauth/token", fn conn ->
+ conn = fetch_conn_params(conn)
+ send(test_pid, {:request, conn})
+ Plug.Conn.resp(conn, 401, Jason.encode!(attrs))
+ end)
+ end
+
+ def expect_userinfo(bypass, attrs \\ %{}) do
+ test_pid = self()
+
+ Bypass.expect(bypass, "GET", "/userinfo", fn conn ->
+ attrs =
+ Map.merge(
+ %{
+ "sub" => "353690423699814251281",
+ "name" => "Ada Lovelace",
+ "given_name" => "Ada",
+ "family_name" => "Lovelace",
+ "picture" =>
+ "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
+ "email" => "ada@example.com",
+ "email_verified" => true,
+ "locale" => "en"
+ },
+ attrs
+ )
+
+ conn = fetch_conn_params(conn)
+ send(test_pid, {:request, conn})
+ Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
+ end)
+ end
+
+ def discovery_document_server do
+ bypass = Bypass.open()
+ endpoint = "http://localhost:#{bypass.port}"
+ test_pid = self()
+
+ Bypass.stub(bypass, "GET", "/.well-known/jwks.json", fn conn ->
+ attrs = %{"keys" => [jwks_attrs()]}
+ Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
+ end)
+
+ Bypass.stub(bypass, "GET", "/.well-known/openid-configuration", fn conn ->
+ conn = fetch_conn_params(conn)
+ send(test_pid, {:request, conn})
+
+ attrs = %{
+ "issuer" => "#{endpoint}/",
+ "authorization_endpoint" => "#{endpoint}/authorize",
+ "token_endpoint" => "#{endpoint}/oauth/token",
+ "device_authorization_endpoint" => "#{endpoint}/oauth/device/code",
+ "userinfo_endpoint" => "#{endpoint}/userinfo",
+ "mfa_challenge_endpoint" => "#{endpoint}/mfa/challenge",
+ "jwks_uri" => "#{endpoint}/.well-known/jwks.json",
+ "registration_endpoint" => "#{endpoint}/oidc/register",
+ "revocation_endpoint" => "#{endpoint}/oauth/revoke",
+ "end_session_endpoint" => "https://example.com",
+ "scopes_supported" => [
+ "openid",
+ "profile",
+ "offline_access",
+ "name",
+ "given_name",
+ "family_name",
+ "nickname",
+ "email",
+ "email_verified",
+ "picture",
+ "created_at",
+ "identities",
+ "phone",
+ "address"
+ ],
+ "response_types_supported" => [
+ "code",
+ "token",
+ "id_token",
+ "code token",
+ "code id_token",
+ "token id_token",
+ "code token id_token"
+ ],
+ "code_challenge_methods_supported" => [
+ "S256",
+ "plain"
+ ],
+ "response_modes_supported" => [
+ "query",
+ "fragment",
+ "form_post"
+ ],
+ "subject_types_supported" => [
+ "public"
+ ],
+ "id_token_signing_alg_values_supported" => [
+ "HS256",
+ "RS256"
+ ],
+ "token_endpoint_auth_methods_supported" => [
+ "client_secret_basic",
+ "client_secret_post"
+ ],
+ "claims_supported" => [
+ "aud",
+ "auth_time",
+ "created_at",
+ "email",
+ "email_verified",
+ "exp",
+ "family_name",
+ "given_name",
+ "iat",
+ "identities",
+ "iss",
+ "name",
+ "nickname",
+ "phone_number",
+ "picture",
+ "sub"
+ ],
+ "request_uri_parameter_supported" => false,
+ "request_parameter_supported" => false
+ }
+
+ Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
+ end)
+
+ {bypass, "#{endpoint}/.well-known/openid-configuration"}
+ end
+
+ def fetch_conn_params(conn) do
+ opts = Plug.Parsers.init(parsers: [:urlencoded, :json], pass: ["*/*"], json_decoder: Jason)
+
+ conn
+ |> Plug.Conn.fetch_query_params()
+ |> Plug.Parsers.call(opts)
+ end
+
defp counter do
System.unique_integer([:positive])
end
diff --git a/apps/domain/test/support/fixtures/config_fixtures.ex b/apps/domain/test/support/fixtures/config_fixtures.ex
index a47b44a6b..2181ea98c 100644
--- a/apps/domain/test/support/fixtures/config_fixtures.ex
+++ b/apps/domain/test/support/fixtures/config_fixtures.ex
@@ -1,350 +1,31 @@
defmodule Domain.ConfigFixtures do
- @moduledoc """
- Allows for easily updating configuration in tests.
- """
- alias Domain.Repo
alias Domain.Config
+ alias Domain.AccountsFixtures
+
+ def configuration_attrs(attrs \\ %{}) do
+ Enum.into(attrs, %{
+ devices_upstream_dns: ["1.1.1.1"]
+ })
+ end
+
+ def upsert_configuration(attrs \\ %{}) do
+ attrs = Enum.into(attrs, %{})
+
+ {account, attrs} =
+ Map.pop_lazy(attrs, :account, fn ->
+ AccountsFixtures.create_account()
+ end)
+
+ attrs = configuration_attrs(attrs)
- def configuration(%Config.Configuration{} = conf \\ Config.fetch_db_config!(), attrs) do
{:ok, configuration} =
- conf
- |> Config.Configuration.Changeset.changeset(attrs)
- |> Repo.update()
+ Config.get_account_config_by_account_id(account.id)
+ |> Config.update_config(attrs)
configuration
end
- def start_openid_providers(provider_names, overrides \\ %{}) do
- {bypass, discovery_document_url} = discovery_document_server()
-
- openid_connect_providers_attrs =
- discovery_document_url
- |> openid_connect_providers_attrs()
- |> Enum.filter(&(&1["id"] in provider_names))
- |> Enum.map(fn config ->
- config
- |> Enum.into(%{})
- |> Map.merge(overrides)
- end)
-
- {bypass, openid_connect_providers_attrs}
- end
-
- def openid_connect_provider_attrs(overrides \\ %{}) do
- Enum.into(overrides, %{
- "id" => "google",
- "discovery_document_uri" => "https://firezone.example.com/.well-known/openid-configuration",
- "client_id" => "google-client-id",
- "client_secret" => "google-client-secret",
- "redirect_uri" => "https://firezone.example.com/auth/oidc/google/callback/",
- "response_type" => "code",
- "scope" => "openid email profile",
- "label" => "OIDC Google",
- "auto_create_users" => false
- })
- end
-
- defp openid_connect_providers_attrs(discovery_document_url) do
- [
- %{
- "id" => "google",
- "discovery_document_uri" => discovery_document_url,
- "client_id" => "google-client-id",
- "client_secret" => "google-client-secret",
- "redirect_uri" => "https://firezone.example.com/auth/oidc/google/callback/",
- "response_type" => "code",
- "scope" => "openid email profile",
- "label" => "OIDC Google",
- "auto_create_users" => false
- },
- %{
- "id" => "okta",
- "discovery_document_uri" => discovery_document_url,
- "client_id" => "okta-client-id",
- "client_secret" => "okta-client-secret",
- "redirect_uri" => "https://firezone.example.com/auth/oidc/okta/callback/",
- "response_type" => "code",
- "scope" => "openid email profile offline_access",
- "label" => "OIDC Okta",
- "auto_create_users" => false
- },
- %{
- "id" => "auth0",
- "discovery_document_uri" => discovery_document_url,
- "client_id" => "auth0-client-id",
- "client_secret" => "auth0-client-secret",
- "redirect_uri" => "https://firezone.example.com/auth/oidc/auth0/callback/",
- "response_type" => "code",
- "scope" => "openid email profile",
- "label" => "OIDC Auth0",
- "auto_create_users" => false
- },
- %{
- "id" => "azure",
- "discovery_document_uri" => discovery_document_url,
- "client_id" => "azure-client-id",
- "client_secret" => "azure-client-secret",
- "redirect_uri" => "https://firezone.example.com/auth/oidc/azure/callback/",
- "response_type" => "code",
- "scope" => "openid email profile offline_access",
- "label" => "OIDC Azure",
- "auto_create_users" => false
- },
- %{
- "id" => "onelogin",
- "discovery_document_uri" => discovery_document_url,
- "client_id" => "onelogin-client-id",
- "client_secret" => "onelogin-client-secret",
- "redirect_uri" => "https://firezone.example.com/auth/oidc/onelogin/callback/",
- "response_type" => "code",
- "scope" => "openid email profile offline_access",
- "label" => "OIDC Onelogin",
- "auto_create_users" => false
- },
- %{
- "id" => "keycloak",
- "discovery_document_uri" => discovery_document_url,
- "client_id" => "keycloak-client-id",
- "client_secret" => "keycloak-client-secret",
- "redirect_uri" => "https://firezone.example.com/auth/oidc/keycloak/callback/",
- "response_type" => "code",
- "scope" => "openid email profile offline_access",
- "label" => "OIDC Keycloak",
- "auto_create_users" => false
- },
- %{
- "id" => "vault",
- "discovery_document_uri" => discovery_document_url,
- "client_id" => "vault-client-id",
- "client_secret" => "vault-client-secret",
- "redirect_uri" => "https://firezone.example.com/auth/oidc/vault/callback/",
- "response_type" => "code",
- "scope" => "openid email profile offline_access",
- "label" => "OIDC Vault",
- "auto_create_users" => false
- }
- ]
- end
-
- def jwks_attrs do
- %{
- "alg" => "RS256",
- "d" =>
- "X8TM24Zqbiha9geYYk_vZpANu16IadJLJLJ7ucTc3JaMbK8NCYNcHMoXKnNYPFxmq-UWAEIwh-2" <>
- "txOiOxuChVrblpfyE4SBJio1T0AUcCwmm8U6G-CsSHMMzWTt2dMTnArHjdyAIgOVRW5SVzhTT" <>
- "taf4JY-47S-fbcJ7g0hmBbVih5i1sE2fad4I4qFHT-YFU_pnUHbteR6GQuRW4r03Eon8Aje6a" <>
- "l2AxcYnfF8_cSOIOpkDgGavTtGYhhZPi2jZ7kPm6QGkNW5CyfEq5PGB6JOihw-XIFiiMzYgx0" <>
- "52rnzoqALoLheXrI0By4kgHSmcqOOmq7aiOff45rlSbpsR",
- "e" => "AQAB",
- "kid" => "example@firezone.dev",
- "kty" => "RSA",
- "n" =>
- "qlKll8no4lPYXNSuTTnacpFHiXwPOv_htCYvIXmiR7CWhiiOHQqj7KWXIW7TGxyoLVIyeRM4mwv" <>
- "kLI-UgsSMYdEKTT0j7Ydjrr0zCunPu5Gxr2yOmcRaszAzGxJL5DwpA0V40RqMlm5OuwdqS4To" <>
- "_p9LlLxzMF6RZe1OqslV5RZ4Y8FmrWq6BV98eIziEHL0IKdsAIrrOYkkcLDdQeMNuTp_yNB8X" <>
- "l2TdWSdsbRomrs2dCtCqZcXTsy2EXDceHvYhgAB33nh_w17WLrZQwMM-7kJk36Kk54jZd7i80" <>
- "AJf_s_plXn1mEh-L5IAL1vg3a9EOMFUl-lPiGqc3td_ykH",
- "use" => "sig"
- }
- end
-
- def expect_refresh_token(bypass, attrs \\ %{}) do
- test_pid = self()
-
- Bypass.expect(bypass, "POST", "/oauth/token", fn conn ->
- conn = fetch_conn_params(conn)
- send(test_pid, {:request, conn})
- Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
- end)
- end
-
- def expect_refresh_token_failure(bypass, attrs \\ %{}) do
- test_pid = self()
-
- Bypass.expect(bypass, "POST", "/oauth/token", fn conn ->
- conn = fetch_conn_params(conn)
- send(test_pid, {:request, conn})
- Plug.Conn.resp(conn, 401, Jason.encode!(attrs))
- end)
- end
-
- def expect_userinfo(bypass, attrs \\ %{}) do
- test_pid = self()
-
- Bypass.expect(bypass, "GET", "/userinfo", fn conn ->
- attrs =
- Map.merge(
- %{
- "sub" => "353690423699814251281",
- "name" => "Ada Lovelace",
- "given_name" => "Ada",
- "family_name" => "Lovelace",
- "picture" =>
- "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
- "email" => "ada@example.com",
- "email_verified" => true,
- "locale" => "en"
- },
- attrs
- )
-
- conn = fetch_conn_params(conn)
- send(test_pid, {:request, conn})
- Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
- end)
- end
-
- def discovery_document_server do
- bypass = Bypass.open()
- endpoint = "http://localhost:#{bypass.port}"
- test_pid = self()
-
- Bypass.stub(bypass, "GET", "/.well-known/jwks.json", fn conn ->
- attrs = %{"keys" => [jwks_attrs()]}
- Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
- end)
-
- Bypass.stub(bypass, "GET", "/.well-known/openid-configuration", fn conn ->
- conn = fetch_conn_params(conn)
- send(test_pid, {:request, conn})
-
- attrs = %{
- "issuer" => "#{endpoint}/",
- "authorization_endpoint" => "#{endpoint}/authorize",
- "token_endpoint" => "#{endpoint}/oauth/token",
- "device_authorization_endpoint" => "#{endpoint}/oauth/device/code",
- "userinfo_endpoint" => "#{endpoint}/userinfo",
- "mfa_challenge_endpoint" => "#{endpoint}/mfa/challenge",
- "jwks_uri" => "#{endpoint}/.well-known/jwks.json",
- "registration_endpoint" => "#{endpoint}/oidc/register",
- "revocation_endpoint" => "#{endpoint}/oauth/revoke",
- "end_session_endpoint" => "https://example.com",
- "scopes_supported" => [
- "openid",
- "profile",
- "offline_access",
- "name",
- "given_name",
- "family_name",
- "nickname",
- "email",
- "email_verified",
- "picture",
- "created_at",
- "identities",
- "phone",
- "address"
- ],
- "response_types_supported" => [
- "code",
- "token",
- "id_token",
- "code token",
- "code id_token",
- "token id_token",
- "code token id_token"
- ],
- "code_challenge_methods_supported" => [
- "S256",
- "plain"
- ],
- "response_modes_supported" => [
- "query",
- "fragment",
- "form_post"
- ],
- "subject_types_supported" => [
- "public"
- ],
- "id_token_signing_alg_values_supported" => [
- "HS256",
- "RS256"
- ],
- "token_endpoint_auth_methods_supported" => [
- "client_secret_basic",
- "client_secret_post"
- ],
- "claims_supported" => [
- "aud",
- "auth_time",
- "created_at",
- "email",
- "email_verified",
- "exp",
- "family_name",
- "given_name",
- "iat",
- "identities",
- "iss",
- "name",
- "nickname",
- "phone_number",
- "picture",
- "sub"
- ],
- "request_uri_parameter_supported" => false,
- "request_parameter_supported" => false
- }
-
- Plug.Conn.resp(conn, 200, Jason.encode!(attrs))
- end)
-
- {bypass, "#{endpoint}/.well-known/openid-configuration"}
- end
-
- def fetch_conn_params(conn) do
- opts = Plug.Parsers.init(parsers: [:urlencoded, :json], pass: ["*/*"], json_decoder: Jason)
-
- conn
- |> Plug.Conn.fetch_query_params()
- |> Plug.Parsers.call(opts)
- end
-
- def saml_identity_providers_attrs(attrs \\ %{}) do
- Enum.into(attrs, %{
- "metadata" => saml_metadata(),
- "label" => "test",
- "id" => "test",
- "auto_create_users" => true
- })
- end
-
- def saml_metadata do
- """
-
-
-
-
-
-
- MIIDqDCCApCgAwIBAgIGAYMaIfiKMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJVUzETMBEG
- A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
- MBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi04Mzg1OTk1NTEcMBoGCSqGSIb3DQEJ
- ARYNaW5mb0Bva3RhLmNvbTAeFw0yMjA5MDcyMjQ1MTdaFw0zMjA5MDcyMjQ2MTdaMIGUMQswCQYD
- VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsG
- A1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi04Mzg1OTk1NTEc
- MBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
- ggEBAOmj276L3kHm57hNGYTocT6NS4mffPbcvsA2UuKIWfmpV8HLTcmS+NahLtuN841OnRnTn+2p
- fjlwa1mwJhCODbF3dcVYOkGTPUC4y2nvf1Xas6M7+0O2WIfrzdX/OOUs/ROMnB/O/MpBwMR2SQh6
- Q3V+9v8g3K9yfMvcifDbl6g9fTliDzqV7I9xF5eJykl+iCAKNaQgp3cO6TaIa5u2ZKtRAdzwnuJC
- BXMyzaoNs/vfnwzuFtzWP1PSS1Roan+8AMwkYA6BCr1YRIqZ0GSkr/qexFCTZdq0UnSN78fY6CCM
- RFw5wU0WM9nEpbWzkBBWsYHeTLo5JqR/mZukfjlPDlcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
- lUhwzCSnuqt4wlHxJONN4kxUBG8bPnjHxob6jBKK+onFDuSVWZ+7LZw67blz6xdxvlOLaQLi1fK2
- Fifehbc7KbRLckcgNgg7Y8qfUKdP0/nS0JlyAvlnICQqaHTHwhIzQqTHtTZeeIJHtpWOX/OPRI0S
- bkygh2qjF8bYn3sX8bGNUQL8iiMxFnvwGrXaErPqlRqFJbWQDBXD+nYDIBw7WN3Jyb0Ydin2zrlh
- gp3Qooi0TnAir3ncw/UF/+sivCgd+6nX7HkbZtipkMbg7ZByyD9xrOQG2JXrP6PyzGCPwnGMt9pL
- iiVMepeLNqKZ3UvhrR1uRN0KWu7lduIRhxldLA==
-
-
-
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
-
-
-
-
- """
+ def set_config(account, key, value) do
+ upsert_configuration([{:account, account}, {key, value}])
end
end
diff --git a/apps/domain/test/support/fixtures/clients_fixtures.ex b/apps/domain/test/support/fixtures/devices_fixtures.ex
similarity index 62%
rename from apps/domain/test/support/fixtures/clients_fixtures.ex
rename to apps/domain/test/support/fixtures/devices_fixtures.ex
index 7d60724e5..42af1fb55 100644
--- a/apps/domain/test/support/fixtures/clients_fixtures.ex
+++ b/apps/domain/test/support/fixtures/devices_fixtures.ex
@@ -1,17 +1,17 @@
-defmodule Domain.ClientsFixtures do
+defmodule Domain.DevicesFixtures do
alias Domain.Repo
- alias Domain.Clients
+ alias Domain.Devices
alias Domain.{AccountsFixtures, ActorsFixtures, AuthFixtures}
- def client_attrs(attrs \\ %{}) do
+ def device_attrs(attrs \\ %{}) do
Enum.into(attrs, %{
external_id: Ecto.UUID.generate(),
- name: "client-#{counter()}",
+ name: "device-#{counter()}",
public_key: public_key()
})
end
- def create_client(attrs \\ %{}) do
+ def create_device(attrs \\ %{}) do
attrs = Enum.into(attrs, %{})
{account, attrs} =
@@ -25,7 +25,7 @@ defmodule Domain.ClientsFixtures do
{actor, attrs} =
Map.pop_lazy(attrs, :actor, fn ->
- ActorsFixtures.create_actor(role: :admin, account: account)
+ ActorsFixtures.create_actor(type: :account_admin_user, account: account)
end)
{identity, attrs} =
@@ -38,19 +38,19 @@ defmodule Domain.ClientsFixtures do
AuthFixtures.create_subject(identity)
end)
- attrs = client_attrs(attrs)
+ attrs = device_attrs(attrs)
- {:ok, client} = Clients.upsert_client(attrs, subject)
- client
+ {:ok, device} = Devices.upsert_device(attrs, subject)
+ device
end
- def delete_client(client) do
- client = Repo.preload(client, :account)
- actor = ActorsFixtures.create_actor(role: :admin, account: client.account)
- identity = AuthFixtures.create_identity(account: client.account, actor: actor)
+ def delete_device(device) do
+ device = Repo.preload(device, :account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: device.account)
+ identity = AuthFixtures.create_identity(account: device.account, actor: actor)
subject = AuthFixtures.create_subject(identity)
- {:ok, client} = Clients.delete_client(client, subject)
- client
+ {:ok, device} = Devices.delete_device(device, subject)
+ device
end
def public_key do
diff --git a/apps/domain/test/support/fixtures/gateways_fixtures.ex b/apps/domain/test/support/fixtures/gateways_fixtures.ex
index eb0f55a66..f48c7f954 100644
--- a/apps/domain/test/support/fixtures/gateways_fixtures.ex
+++ b/apps/domain/test/support/fixtures/gateways_fixtures.ex
@@ -22,7 +22,7 @@ defmodule Domain.GatewaysFixtures do
{subject, attrs} =
Map.pop_lazy(attrs, :subject, fn ->
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
AuthFixtures.create_subject(identity)
end)
@@ -35,7 +35,7 @@ defmodule Domain.GatewaysFixtures do
def delete_group(group) do
group = Repo.preload(group, :account)
- actor = ActorsFixtures.create_actor(role: :admin, account: group.account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: group.account)
identity = AuthFixtures.create_identity(account: group.account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, group} = Gateways.delete_group(group, subject)
@@ -107,7 +107,7 @@ defmodule Domain.GatewaysFixtures do
def delete_gateway(gateway) do
gateway = Repo.preload(gateway, :account)
- actor = ActorsFixtures.create_actor(role: :admin, account: gateway.account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: gateway.account)
identity = AuthFixtures.create_identity(account: gateway.account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, gateway} = Gateways.delete_gateway(gateway, subject)
diff --git a/apps/domain/test/support/fixtures/relays_fixtures.ex b/apps/domain/test/support/fixtures/relays_fixtures.ex
index 04c2eec54..04d905245 100644
--- a/apps/domain/test/support/fixtures/relays_fixtures.ex
+++ b/apps/domain/test/support/fixtures/relays_fixtures.ex
@@ -20,7 +20,7 @@ defmodule Domain.RelaysFixtures do
{subject, attrs} =
Map.pop_lazy(attrs, :subject, fn ->
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
AuthFixtures.create_subject(identity)
end)
@@ -33,7 +33,7 @@ defmodule Domain.RelaysFixtures do
def delete_group(group) do
group = Repo.preload(group, :account)
- actor = ActorsFixtures.create_actor(role: :admin, account: group.account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: group.account)
identity = AuthFixtures.create_identity(account: group.account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, group} = Relays.delete_group(group, subject)
@@ -106,7 +106,7 @@ defmodule Domain.RelaysFixtures do
def delete_relay(relay) do
relay = Repo.preload(relay, :account)
- actor = ActorsFixtures.create_actor(role: :admin, account: relay.account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: relay.account)
identity = AuthFixtures.create_identity(account: relay.account, actor: actor)
subject = AuthFixtures.create_subject(identity)
{:ok, relay} = Relays.delete_relay(relay, subject)
diff --git a/apps/domain/test/support/fixtures/resources_fixtures.ex b/apps/domain/test/support/fixtures/resources_fixtures.ex
index 2e4e6a255..9eb3b2c28 100644
--- a/apps/domain/test/support/fixtures/resources_fixtures.ex
+++ b/apps/domain/test/support/fixtures/resources_fixtures.ex
@@ -33,7 +33,7 @@ defmodule Domain.ResourcesFixtures do
{subject, attrs} =
Map.pop_lazy(attrs, :subject, fn ->
- actor = ActorsFixtures.create_actor(role: :admin, account: account)
+ actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account)
identity = AuthFixtures.create_identity(account: account, actor: actor)
AuthFixtures.create_subject(identity)
end)
diff --git a/apps/web/mix.exs b/apps/web/mix.exs
index 1f8764f59..52cef96a4 100644
--- a/apps/web/mix.exs
+++ b/apps/web/mix.exs
@@ -28,7 +28,7 @@ defmodule Web.MixProject do
]
end
- defp elixirc_paths(:test), do: ["lib", "test/support"]
+ defp elixirc_paths(:test), do: ["test/support", "lib"]
defp elixirc_paths(_), do: ["lib"]
defp deps do
@@ -75,7 +75,11 @@ defmodule Web.MixProject do
defp aliases do
[
setup: ["deps.get", "assets.setup", "assets.build"],
- "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
+ "assets.setup": [
+ "cmd cd assets && yarn install",
+ "tailwind.install --if-missing",
+ "esbuild.install --if-missing"
+ ],
"assets.build": ["tailwind default", "esbuild default"],
"assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"],
"ecto.seed": ["ecto.create", "ecto.migrate", "run ../domain/priv/repo/seeds.exs"],
diff --git a/apps/web/test/support/acceptance_case/vault.ex b/apps/web/test/support/acceptance_case/vault.ex
index a93a29ed2..806faa688 100644
--- a/apps/web/test/support/acceptance_case/vault.ex
+++ b/apps/web/test/support/acceptance_case/vault.ex
@@ -26,7 +26,11 @@ defmodule Web.AcceptanceCase.Vault do
:ok
end
- def setup_oidc_provider(endpoint_url, attrs_overrides \\ %{"auto_create_users" => true}) do
+ def setup_oidc_provider(
+ account,
+ endpoint_url,
+ attrs_overrides \\ %{"auto_create_users" => true}
+ ) do
:ok =
request(:put, "identity/oidc/client/firezone", %{
assignments: "allow_all",
@@ -50,7 +54,8 @@ defmodule Web.AcceptanceCase.Vault do
{:ok, {200, params}} = request(:get, "identity/oidc/client/firezone")
- Domain.Config.put_config!(
+ Domain.ConfigFixtures.set_config(
+ account,
:openid_connect_providers,
[
%{
diff --git a/apps/web/test/web/controllers/browser_controller_test.exs b/apps/web/test/web/controllers/browser_controller_test.exs
index 047e78be0..d176563f5 100644
--- a/apps/web/test/web/controllers/browser_controller_test.exs
+++ b/apps/web/test/web/controllers/browser_controller_test.exs
@@ -3,11 +3,7 @@ defmodule Web.BrowserControllerTest do
describe "config/2" do
test "returns valid XML browser config", %{conn: conn} do
- test_conn =
- conn
- # |> put_req_header("accept", "application/xml")
- |> get(~p"/browser/config.xml")
-
+ test_conn = get(conn, ~p"/browser/config.xml")
assert response(test_conn, 200) =~ " ["xml"]
-}
-
-config :web, Web.SAML,
- entity_id: "urn:firezone.dev:firezone-app",
- certfile_path: Path.expand("../apps/web/priv/cert/saml_selfsigned.pem", __DIR__),
- keyfile_path: Path.expand("../apps/web/priv/cert/saml_selfsigned_key.pem", __DIR__)
-
config :web,
cookie_secure: false,
cookie_signing_salt: "WjllcThpb2Y=",
@@ -144,20 +117,14 @@ config :api,
cookie_signing_salt: "WjllcThpb2Y=",
cookie_encryption_salt: "M0EzM0R6NEMyaw=="
-config :api, API.Gateway.Socket,
- key_base: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5SD",
- salt: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDejX",
- max_age: 30 * 60
-
-config :api, API.Relay.Socket,
- key_base: "5OVYJ83AcoQcPmdKNksuBhJFBhjHD1uUa9mDOHV/6EIdBQ6pXksIhkVeWIzFk5SD",
- salt: "t01wa0K4lUd7mKa0HAtZdE+jFOPDDejX",
- max_age: 30 * 60
-
###############################
##### Third-party configs #####
###############################
+config :mime, :types, %{
+ "application/xml" => ["xml"]
+}
+
config :logger, :console,
level: String.to_atom(System.get_env("LOG_LEVEL", "info")),
format: "$time $metadata[$level] $message\n",
@@ -171,23 +138,6 @@ config :posthog,
api_url: "https://t.firez.one",
api_key: "phc_ubuPhiqqjMdedpmbWpG2Ak3axqv5eMVhFDNBaXl9UZK"
-# Configures the vault
-config :domain, Domain.Vault,
- ciphers: [
- default: {
- Cloak.Ciphers.AES.GCM,
- # In AES.GCM, it is important to specify 12-byte IV length for
- # interoperability with other encryption software. See this GitHub
- # issue for more details:
- # https://github.com/danielberkompas/cloak/issues/93
- #
- # In Cloak 2.0, this will be the default iv length for AES.GCM.
- tag: "AES.GCM.V1",
- key: Base.decode64!("XXJ/NGevpvkG9219RYsz21zZWR7CZ//CqA0ARPIBqys="),
- iv_length: 12
- }
- ]
-
config :web, Web.Mailer,
adapter: Web.Mailer.NoopAdapter,
from_email: "test@firez.one"
diff --git a/config/prod.exs b/config/prod.exs
index 4559f08e4..70c645de2 100644
--- a/config/prod.exs
+++ b/config/prod.exs
@@ -8,8 +8,6 @@ config :domain, Domain.Repo,
pool_size: 10,
show_sensitive_data_on_connection_error: false
-config :domain, Domain.ConnectivityChecks, url: "https://ping.firez.one/"
-
###############################
##### Web #####################
###############################
diff --git a/config/runtime.exs b/config/runtime.exs
index 4f930d330..917729c58 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -16,7 +16,8 @@ if config_env() == :prod do
pool_size: compile_config!(:database_pool_size),
ssl: compile_config!(:database_ssl_enabled),
ssl_opts: compile_config!(:database_ssl_opts),
- parameters: compile_config!(:database_parameters)
+ parameters: compile_config!(:database_parameters),
+ show_sensitive_data_on_connection_error: false
external_url = compile_config!(:external_url)
@@ -27,29 +28,25 @@ if config_env() == :prod do
path: external_url_path
} = URI.parse(external_url)
- config :domain,
- wireguard_ipv4_enabled: compile_config!(:wireguard_ipv4_enabled),
- wireguard_ipv4_network: compile_config!(:wireguard_ipv4_network),
- wireguard_ipv4_address: compile_config!(:wireguard_ipv4_address),
- wireguard_ipv6_enabled: compile_config!(:wireguard_ipv6_enabled),
- wireguard_ipv6_network: compile_config!(:wireguard_ipv6_network),
- wireguard_ipv6_address: compile_config!(:wireguard_ipv6_address)
+ config :domain, Domain.Devices, upstream_dns: compile_config!(:devices_upstream_dns)
+
+ config :domain, Domain.Gateways,
+ gateway_ipv4_masquerade: compile_config!(:gateway_ipv4_masquerade),
+ gateway_ipv6_masquerade: compile_config!(:gateway_ipv6_masquerade),
+ key_base: compile_config!(:gateways_auth_token_key_base),
+ salt: compile_config!(:gateways_auth_token_salt)
+
+ config :domain, Domain.Relays,
+ key_base: compile_config!(:relays_auth_token_key_base),
+ salt: compile_config!(:relays_auth_token_salt)
config :domain, Domain.Telemetry,
enabled: compile_config!(:telemetry_enabled),
id: compile_config!(:telemetry_id)
- config :domain, Domain.ConnectivityChecks,
- http_client_options: compile_config!(:http_client_ssl_opts),
- enabled: compile_config!(:connectivity_checks_enabled),
- interval: compile_config!(:connectivity_checks_interval)
-
- config :domain,
- admin_email: compile_config!(:default_admin_email),
- default_admin_password: compile_config!(:default_admin_password)
-
- config :domain,
- max_devices_per_user: compile_config!(:max_devices_per_user)
+ config :domain, Domain.Auth,
+ key_base: compile_config!(:auth_token_key_base),
+ salt: compile_config!(:auth_token_salt)
###############################
##### Web #####################
@@ -63,7 +60,7 @@ if config_env() == :prod do
server: true,
http: [
ip: compile_config!(:phoenix_listen_address).address,
- port: compile_config!(:phoenix_http_port),
+ port: compile_config!(:phoenix_http_web_port),
protocol_options: compile_config!(:phoenix_http_protocol_options)
],
url: [
@@ -87,14 +84,14 @@ if config_env() == :prod do
cookie_encryption_salt: compile_config!(:cookie_encryption_salt)
###############################
- ##### Web #####################
+ ##### API #####################
###############################
- config :web, Web.Endpoint,
+ config :api, API.Endpoint,
server: true,
http: [
ip: compile_config!(:phoenix_listen_address).address,
- port: compile_config!(:phoenix_http_port),
+ port: compile_config!(:phoenix_http_api_port),
protocol_options: compile_config!(:phoenix_http_protocol_options)
],
# TODO: force_ssl: [rewrite_on: [:x_forwarded_proto], hsts: true],
@@ -115,38 +112,9 @@ if config_env() == :prod do
##### Third-party configs #####
###############################
- config :web, Web.Auth.HTML.Authentication, secret_key: compile_config!(:guardian_secret_key)
-
- config :web, Web.Auth.JSON.Authentication, secret_key: compile_config!(:guardian_secret_key)
-
- config :domain, Domain.Vault,
- ciphers: [
- default: {
- Cloak.Ciphers.AES.GCM,
- # In AES.GCM, it is important to specify 12-byte IV length for
- # interoperability with other encryption software. See this GitHub
- # issue for more details:
- # https://github.com/danielberkompas/cloak/issues/93
- #
- # In Cloak 2.0, this will be the default iv length for AES.GCM.
- tag: "AES.GCM.V1",
- key: Base.decode64!(compile_config!(:database_encryption_key)),
- iv_length: 12
- }
- ]
-
config :openid_connect,
finch_transport_opts: compile_config!(:http_client_ssl_opts)
- config :ueberauth, Ueberauth,
- providers: [
- identity:
- {Ueberauth.Strategy.Identity,
- callback_methods: ["POST"],
- callback_url: "#{external_url}/auth/identity/callback",
- uid_field: :email}
- ]
-
config :web,
Web.Mailer,
[
diff --git a/mix.exs b/mix.exs
index cd34087b3..5da1f3c3b 100644
--- a/mix.exs
+++ b/mix.exs
@@ -28,15 +28,13 @@ defmodule Firezone.MixProject do
plt_file: {:no_warn, "priv/plts/dialyzer.plt"}
],
aliases: aliases(),
- default_release: :firezone,
+ default_release: :web,
releases: [
- firezone: [
+ web: [
include_executables_for: [:unix],
- validate_compile_env: false,
+ validate_compile_env: true,
applications: [
- domain: :permanent,
- web: :permanent,
- api: :permanent
+ web: :permanent
],
cookie: System.get_env("ERL_COOKIE")
]
diff --git a/priv/keycloak-realm.json b/priv/keycloak-realm.json
deleted file mode 100644
index 82bf258c1..000000000
--- a/priv/keycloak-realm.json
+++ /dev/null
@@ -1,2215 +0,0 @@
-{
- "id": "a22cc33d-5ad3-452d-a022-b4f1a1745af3",
- "realm": "firezone",
- "displayName": "",
- "displayNameHtml": "",
- "notBefore": 0,
- "defaultSignatureAlgorithm": "RS256",
- "revokeRefreshToken": false,
- "refreshTokenMaxReuse": 0,
- "accessTokenLifespan": 300,
- "accessTokenLifespanForImplicitFlow": 900,
- "ssoSessionIdleTimeout": 1800,
- "ssoSessionMaxLifespan": 36000,
- "ssoSessionIdleTimeoutRememberMe": 0,
- "ssoSessionMaxLifespanRememberMe": 0,
- "offlineSessionIdleTimeout": 2592000,
- "offlineSessionMaxLifespanEnabled": false,
- "offlineSessionMaxLifespan": 5184000,
- "clientSessionIdleTimeout": 0,
- "clientSessionMaxLifespan": 0,
- "clientOfflineSessionIdleTimeout": 0,
- "clientOfflineSessionMaxLifespan": 0,
- "accessCodeLifespan": 60,
- "accessCodeLifespanUserAction": 300,
- "accessCodeLifespanLogin": 1800,
- "actionTokenGeneratedByAdminLifespan": 43200,
- "actionTokenGeneratedByUserLifespan": 300,
- "oauth2DeviceCodeLifespan": 600,
- "oauth2DevicePollingInterval": 5,
- "enabled": true,
- "sslRequired": "none",
- "registrationAllowed": false,
- "registrationEmailAsUsername": false,
- "rememberMe": false,
- "verifyEmail": false,
- "loginWithEmailAllowed": true,
- "duplicateEmailsAllowed": false,
- "resetPasswordAllowed": false,
- "editUsernameAllowed": false,
- "bruteForceProtected": false,
- "permanentLockout": false,
- "maxFailureWaitSeconds": 900,
- "minimumQuickLoginWaitSeconds": 60,
- "waitIncrementSeconds": 60,
- "quickLoginCheckMilliSeconds": 1000,
- "maxDeltaTimeSeconds": 43200,
- "failureFactor": 30,
- "roles": {
- "realm": [
- {
- "id": "30d85621-c052-4b8e-9dbd-4755dc663ed9",
- "name": "uma_authorization",
- "description": "${role_uma_authorization}",
- "composite": false,
- "clientRole": false,
- "containerId": "a22cc33d-5ad3-452d-a022-b4f1a1745af3",
- "attributes": {}
- },
- {
- "id": "5f6d403c-a7f6-4621-be85-8adda45b0156",
- "name": "offline_access",
- "description": "${role_offline-access}",
- "composite": false,
- "clientRole": false,
- "containerId": "a22cc33d-5ad3-452d-a022-b4f1a1745af3",
- "attributes": {}
- },
- {
- "id": "a57d0e46-7bf5-409b-8a25-9ca52ac40a08",
- "name": "default-roles-firezone",
- "description": "${role_default-roles}",
- "composite": true,
- "composites": {
- "realm": [
- "offline_access",
- "uma_authorization"
- ]
- },
- "clientRole": false,
- "containerId": "a22cc33d-5ad3-452d-a022-b4f1a1745af3",
- "attributes": {}
- }
- ],
- "client": {
- "firezone-saml": [],
- "realm-management": [
- {
- "id": "3af52d56-dc2b-4acd-90ff-1d5d0c358820",
- "name": "manage-events",
- "description": "${role_manage-events}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "939ccaaa-d4f9-4ad7-be50-067478608fa4",
- "name": "query-realms",
- "description": "${role_query-realms}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "98bad0ad-5b0d-471e-b9ed-05fe422f2661",
- "name": "realm-admin",
- "description": "${role_realm-admin}",
- "composite": true,
- "composites": {
- "client": {
- "realm-management": [
- "manage-events",
- "query-realms",
- "view-authorization",
- "manage-realm",
- "view-clients",
- "view-realm",
- "create-client",
- "view-users",
- "view-identity-providers",
- "query-users",
- "query-clients",
- "impersonation",
- "manage-identity-providers",
- "view-events",
- "query-groups",
- "manage-clients",
- "manage-users",
- "manage-authorization"
- ]
- }
- },
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "3b92ec03-5be8-4a6b-90d8-0c95dc60687e",
- "name": "view-authorization",
- "description": "${role_view-authorization}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "1161fb11-c409-4226-a380-1e74959a6422",
- "name": "manage-realm",
- "description": "${role_manage-realm}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "d136c518-c37b-44d2-ba8f-16298c8338a5",
- "name": "view-clients",
- "description": "${role_view-clients}",
- "composite": true,
- "composites": {
- "client": {
- "realm-management": [
- "query-clients"
- ]
- }
- },
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "f81a45ad-35af-43a9-9ef9-0908d4a9c9e9",
- "name": "view-realm",
- "description": "${role_view-realm}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "a3e4e1d8-62fc-40f1-aeb6-524c20f37acb",
- "name": "create-client",
- "description": "${role_create-client}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "f7e53663-083d-4e33-8dbc-01f4d4106aa2",
- "name": "view-users",
- "description": "${role_view-users}",
- "composite": true,
- "composites": {
- "client": {
- "realm-management": [
- "query-groups",
- "query-users"
- ]
- }
- },
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "f9e42700-8edf-4640-ba27-7756fabc89b8",
- "name": "view-identity-providers",
- "description": "${role_view-identity-providers}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "fe9a489c-86c6-4355-8f8b-3b71bccc8cba",
- "name": "query-users",
- "description": "${role_query-users}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "fcb723e1-c1a5-4feb-815f-d949683aaf42",
- "name": "query-clients",
- "description": "${role_query-clients}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "d6626dcc-1866-44ba-b593-aec1e5db6680",
- "name": "impersonation",
- "description": "${role_impersonation}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "f6e6ef50-8946-42ba-b344-f4c124ecd0b6",
- "name": "manage-identity-providers",
- "description": "${role_manage-identity-providers}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "e20b7ed4-d080-4232-9db5-42a080fc703f",
- "name": "query-groups",
- "description": "${role_query-groups}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "6b812331-dc99-4803-bb8e-943db354f352",
- "name": "view-events",
- "description": "${role_view-events}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "522af2d1-55f9-4dd7-baa0-6c387d3bb6ea",
- "name": "manage-clients",
- "description": "${role_manage-clients}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "e61310e2-13d3-4103-b6e5-5ca5e936f0ac",
- "name": "manage-users",
- "description": "${role_manage-users}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- },
- {
- "id": "e5b19f56-2fee-4565-8398-fa6d0893af02",
- "name": "manage-authorization",
- "description": "${role_manage-authorization}",
- "composite": false,
- "clientRole": true,
- "containerId": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "attributes": {}
- }
- ],
- "firezone-client": [],
- "security-admin-console": [],
- "admin-cli": [],
- "account-console": [],
- "broker": [],
- "account": [
- {
- "id": "f844f556-fcbd-4156-940e-830f16d084e7",
- "name": "delete-account",
- "description": "${role_delete-account}",
- "composite": false,
- "clientRole": true,
- "containerId": "836299f4-90e2-4ce0-89c5-a54bdee7a072",
- "attributes": {}
- },
- {
- "id": "910ef7b7-928e-4d2d-8435-67928254aae0",
- "name": "manage-account",
- "composite": false,
- "clientRole": true,
- "containerId": "836299f4-90e2-4ce0-89c5-a54bdee7a072",
- "attributes": {}
- }
- ]
- }
- },
- "groups": [
- {
- "id": "e11b6538-2214-474d-8b33-6bc9c19624fd",
- "name": "admins",
- "path": "/admins",
- "attributes": {},
- "realmRoles": [],
- "clientRoles": {},
- "subGroups": []
- }
- ],
- "defaultRole": {
- "id": "a57d0e46-7bf5-409b-8a25-9ca52ac40a08",
- "name": "default-roles-firezone",
- "description": "${role_default-roles}",
- "composite": true,
- "clientRole": false,
- "containerId": "a22cc33d-5ad3-452d-a022-b4f1a1745af3"
- },
- "requiredCredentials": [
- "password"
- ],
- "otpPolicyType": "totp",
- "otpPolicyAlgorithm": "HmacSHA1",
- "otpPolicyInitialCounter": 0,
- "otpPolicyDigits": 6,
- "otpPolicyLookAheadWindow": 1,
- "otpPolicyPeriod": 30,
- "otpSupportedApplications": [
- "FreeOTP",
- "Google Authenticator"
- ],
- "webAuthnPolicyRpEntityName": "keycloak",
- "webAuthnPolicySignatureAlgorithms": [
- "ES256"
- ],
- "webAuthnPolicyRpId": "",
- "webAuthnPolicyAttestationConveyancePreference": "not specified",
- "webAuthnPolicyAuthenticatorAttachment": "not specified",
- "webAuthnPolicyRequireResidentKey": "not specified",
- "webAuthnPolicyUserVerificationRequirement": "not specified",
- "webAuthnPolicyCreateTimeout": 0,
- "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
- "webAuthnPolicyAcceptableAaguids": [],
- "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
- "webAuthnPolicyPasswordlessSignatureAlgorithms": [
- "ES256"
- ],
- "webAuthnPolicyPasswordlessRpId": "",
- "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
- "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
- "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
- "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
- "webAuthnPolicyPasswordlessCreateTimeout": 0,
- "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
- "webAuthnPolicyPasswordlessAcceptableAaguids": [],
- "scopeMappings": [
- {
- "clientScope": "offline_access",
- "roles": [
- "offline_access"
- ]
- }
- ],
- "clientScopeMappings": {
- "account": [
- {
- "client": "account-console",
- "roles": [
- "manage-account"
- ]
- }
- ]
- },
- "clients": [
- {
- "id": "836299f4-90e2-4ce0-89c5-a54bdee7a072",
- "clientId": "account",
- "name": "${client_account}",
- "rootUrl": "${authBaseUrl}",
- "baseUrl": "/realms/firezone/account/",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "redirectUris": [
- "/realms/firezone/account/*"
- ],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {
- "post.logout.redirect.uris": "+"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "defaultClientScopes": [
- "web-origins",
- "acr",
- "roles",
- "profile",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "2250e9e5-37c5-497e-8920-7bec6c11e5dd",
- "clientId": "account-console",
- "name": "${client_account-console}",
- "rootUrl": "${authBaseUrl}",
- "baseUrl": "/realms/firezone/account/",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "redirectUris": [
- "/realms/firezone/account/*"
- ],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {
- "post.logout.redirect.uris": "+",
- "pkce.code.challenge.method": "S256"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "protocolMappers": [
- {
- "id": "cabb4fe1-3fc5-44c3-ac2c-b16935a9bc85",
- "name": "audience resolve",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-audience-resolve-mapper",
- "consentRequired": false,
- "config": {}
- }
- ],
- "defaultClientScopes": [
- "web-origins",
- "acr",
- "roles",
- "profile",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "692e7a0c-4d27-45ca-bd42-04400d003048",
- "clientId": "admin-cli",
- "name": "${client_admin-cli}",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "redirectUris": [],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": false,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": true,
- "serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {
- "post.logout.redirect.uris": "+"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "defaultClientScopes": [
- "web-origins",
- "acr",
- "roles",
- "profile",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "51d6a9d0-c07e-49a4-ba95-f9418c2dafb0",
- "clientId": "broker",
- "name": "${client_broker}",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "redirectUris": [],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": true,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": false,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {
- "post.logout.redirect.uris": "+"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "defaultClientScopes": [
- "web-origins",
- "acr",
- "roles",
- "profile",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "bd3e5795-d514-44c1-9ad9-7b50c745b25d",
- "clientId": "firezone-client",
- "name": "",
- "description": "",
- "rootUrl": "",
- "adminUrl": "",
- "baseUrl": "",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "secret": "**********",
- "redirectUris": [
- "",
- "https://localhost/auth/oidc/keycloak/callback/"
- ],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": true,
- "serviceAccountsEnabled": false,
- "publicClient": false,
- "frontchannelLogout": true,
- "protocol": "openid-connect",
- "attributes": {
- "oidc.ciba.grant.enabled": "false",
- "client.secret.creation.time": "1665772021",
- "backchannel.logout.session.required": "true",
- "post.logout.redirect.uris": "+",
- "display.on.consent.screen": "false",
- "oauth2.device.authorization.grant.enabled": "false",
- "backchannel.logout.revoke.offline.tokens": "false"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": true,
- "nodeReRegistrationTimeout": -1,
- "defaultClientScopes": [
- "web-origins",
- "acr",
- "roles",
- "profile",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "325df7cb-baac-4733-b601-b4a0baed099a",
- "clientId": "firezone-saml",
- "name": "",
- "description": "",
- "rootUrl": "",
- "adminUrl": "",
- "baseUrl": "",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "redirectUris": [
- "",
- "https://localhost/auth/saml/auth/signin/keycloak/"
- ],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": true,
- "serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": true,
- "protocol": "saml",
- "attributes": {
- "saml.assertion.signature": "false",
- "saml.force.post.binding": "true",
- "saml.server.signature": "true",
- "saml.server.signature.keyinfo.ext": "false",
- "saml.signing.certificate": "MIICqTCCAZECBgGD19uSLjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1maXJlem9uZS1zYW1sMB4XDTIyMTAxNDE4NTUzNFoXDTMyMTAxNDE4NTcxNFowGDEWMBQGA1UEAwwNZmlyZXpvbmUtc2FtbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMBxaPBC4T59NUxZ5A7Q3l10YXP1+I+RvD6U5nTCUQDDKWjgqX20esV21ZIj/FRhst3FzOjFgOmagm2SOSkR6Kejr+hdgfqHPZIKrHUD3Ue1pwPPHbhUjs1NcEYof3FuH+SiaaqEHl5x/i+Vj1uso9Vh0qHxweHyh0YEScBvHecVL9G3ZbtmFgTN6m47I5W6NIopLrCHzCUUxmeb1ihSprTlTlajmefWwLONlwU/V9esvj341SCtH6TInvlOFBN2+QO0UJZZDZ8hu/F7nKoCNai6J/5eWMk1/tFZrRN0moAf7BkUdrT2zGbhZPJfi5hdvE0RpE4gVH3gnOTo9rzr808CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAJBRMH7OcClY9fGQPKGE9XKSelPWiPimdzWES0Z6PKFTd02opg642+WaEs8fPgl6GdJHYWvZ4Ha98+nrihf5APh2sK62RnsEQrIq/PPL5iZ+oT8jn6UEitg123sRrtjoqV9JRiVRfT536sBGmjGRsPyFumygIO5OIth0JjuD2vFvR1PlhlD2HySmFQjtJEPCiK1ETvQjSuqHh8zD1ODv7xjRqy9hqtpUHL+HRSS8tkOg8bEMOtAKUri8xyEbhu5ysQVXjy6kC3fkOk6atFXj6UIhhPhOqIsSsJpk6zbSVJlcpkrbBTL1lhVpN46eh3NybE8NYMWnZthQSqid26feoEw==",
- "saml.artifact.binding.identifier": "lH60OvOihGGt2CFRYdEYO9Kn0p4=",
- "saml.artifact.binding": "false",
- "saml.signature.algorithm": "RSA_SHA256",
- "saml_force_name_id_format": "false",
- "saml.client.signature": "true",
- "saml.force.name.id.format": "false",
- "saml.authnstatement": "true",
- "display.on.consent.screen": "false",
- "saml.signing.private.key": "MIIEoQIBAAKCAQEAwHFo8ELhPn01TFnkDtDeXXRhc/X4j5G8PpTmdMJRAMMpaOCpfbR6xXbVkiP8VGGy3cXM6MWA6ZqCbZI5KRHop6Ov6F2B+oc9kgqsdQPdR7WnA88duFSOzU1wRih/cW4f5KJpqoQeXnH+L5WPW6yj1WHSofHB4fKHRgRJwG8d5xUv0bdlu2YWBM3qbjsjlbo0iikusIfMJRTGZ5vWKFKmtOVOVqOZ59bAs42XBT9X16y+PfjVIK0fpMie+U4UE3b5A7RQllkNnyG78XucqgI1qLon/l5YyTX+0VmtE3SagB/sGRR2tPbMZuFk8l+LmF28TRGkTiBUfeCc5Oj2vOvzTwIDAQABAoH/ck+GmB7KFx9YQmCCnG6UV1jqetGvIr+b17oMqR0/3FHN2UlrjbgaGMI76W2OBzcD/MTyXgcxiq3695nBB6MB0b5WDgyFBds9er822vXJ5Atw8jCpvdhyJsm2DM/QYpwVx4nTewCA/zk15A4I3Qqw3L3GgIgeVnwNzEyT0BkuXbNhBYLQUm/H0aqpU6SPlc7akXQvcP4tcX5RFaImaRdV7Sc5TW2RkjOEn8yuQnk7wq2SwTb0nqmR9o+x7vjjtd1XAY9hKnCFc0BO2bkJwSfiDBN2z1am+MsVegB610uJb0Y/Xx+Ksrr8fjf5ctOf1HmerwEGFZe1Aaim3OQOkEnVAoGBAPlsoZOjOid88LXQ7V2EsUbdoHd19PLxYO3QWUPsy0rxCVY44319g6DobkiY4wunxSk4XPmoeSGxrCxZfFd4GhM3QTnm/sJk7O6eP9licvYKbpdRkz74gWbDJKNrdS0+swmCZMstHNpzgC+3KUGHhZxAsNWQn5odSVT87m0hgwUbAoGBAMWENfk/q0UsgDAPT5zE2ka3Q89xC94azwYvkC2Bt+02WthFzg0LX83k6UL7Ox2dy3A52zHv/+AXII9p8DOQK8eI+Ptk/1FBEXB4BcxfcIknhFwGOQh5mJqGqVKMcjhF0NSlOvvdZ17J0sdg/eFmj16FTc3aD+uTX05xLcOIyVHdAoGAFY9nVBy67SjmocDCk9/hdw+3TDw9BTcKOuRUowN3+y1ksjhOqc1MsH8G8W2Nyrcg2tRNbSM7/UafbGH71o/CKUEQeVHXWbRhqqqxODhDWbNDJf9eLAZNITXjF+E1Csktrre/wLRQly1+uiEqBskRKpWI850Bvs+jRB5s+a/45FsCgYAgmPFPcXKSuAVv6Rb6iIJ9qUFHuyB6R3JjUvY4jHAOdioIB306KeZl1KGqrEvVV3V70BCc7T8NfMOi55j86Y5QIAr3cnOwtUxrg2Nxl067VApEJcLD402MMy0+sm8nWl5cuehCKQgmXKisvxUnXgXLU7TDhHmcJ3ZTO9qRqsGPAQKBgQCxcUwufFqTD5Th8+g9fgBA9248UbPx8zHfQPHUSNw38V7vj2a3LJlcvbWYBFJMad8JwxY0Kai4+Ul+39e3YeAiyJ2gIlhvQFP4renwOA7oUZ9X+zOgw8QAKy80N+SkHMKkC6rBSM7luRymUWIRmTEDcd+yhLE3MMate6J1XRU0uA==",
- "saml_name_id_format": "username",
- "saml.allow.ecp.flow": "false",
- "saml.onetimeuse.condition": "false",
- "saml_signature_canonicalization_method": "http://www.w3.org/2001/10/xml-exc-c14n#"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": true,
- "nodeReRegistrationTimeout": -1,
- "defaultClientScopes": [
- "role_list"
- ],
- "optionalClientScopes": []
- },
- {
- "id": "a0cd08b0-33cf-4d32-a491-a07913b4b2cc",
- "clientId": "realm-management",
- "name": "${client_realm-management}",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "redirectUris": [],
- "webOrigins": [],
- "notBefore": 0,
- "bearerOnly": true,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": false,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {
- "post.logout.redirect.uris": "+"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "defaultClientScopes": [
- "web-origins",
- "acr",
- "roles",
- "profile",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- },
- {
- "id": "c4f8903c-e601-4990-be90-f3cafb8cf094",
- "clientId": "security-admin-console",
- "name": "${client_security-admin-console}",
- "rootUrl": "${authAdminUrl}",
- "baseUrl": "/admin/firezone/console/",
- "surrogateAuthRequired": false,
- "enabled": true,
- "alwaysDisplayInConsole": false,
- "clientAuthenticatorType": "client-secret",
- "redirectUris": [
- "/admin/firezone/console/*"
- ],
- "webOrigins": [
- "+"
- ],
- "notBefore": 0,
- "bearerOnly": false,
- "consentRequired": false,
- "standardFlowEnabled": true,
- "implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
- "serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": false,
- "protocol": "openid-connect",
- "attributes": {
- "post.logout.redirect.uris": "+",
- "pkce.code.challenge.method": "S256"
- },
- "authenticationFlowBindingOverrides": {},
- "fullScopeAllowed": false,
- "nodeReRegistrationTimeout": 0,
- "protocolMappers": [
- {
- "id": "68431c73-23d9-440a-84a8-da997f87bdc4",
- "name": "locale",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "locale",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "locale",
- "jsonType.label": "String"
- }
- }
- ],
- "defaultClientScopes": [
- "web-origins",
- "acr",
- "roles",
- "profile",
- "email"
- ],
- "optionalClientScopes": [
- "address",
- "phone",
- "offline_access",
- "microprofile-jwt"
- ]
- }
- ],
- "clientScopes": [
- {
- "id": "25052823-7eb0-43ed-8dd0-f27cfcb7d0c6",
- "name": "roles",
- "description": "OpenID Connect scope for add user roles to the access token",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "false",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${rolesScopeConsentText}"
- },
- "protocolMappers": [
- {
- "id": "736ab3c2-6474-4cc3-825d-50a8f54ecc2d",
- "name": "realm roles",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-realm-role-mapper",
- "consentRequired": false,
- "config": {
- "user.attribute": "foo",
- "access.token.claim": "true",
- "claim.name": "realm_access.roles",
- "jsonType.label": "String",
- "multivalued": "true"
- }
- },
- {
- "id": "e1795708-fbb2-4ceb-9a08-f291c03da8ad",
- "name": "audience resolve",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-audience-resolve-mapper",
- "consentRequired": false,
- "config": {}
- },
- {
- "id": "7c924754-a88d-459d-b94a-fef37ef3d8e2",
- "name": "client roles",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-client-role-mapper",
- "consentRequired": false,
- "config": {
- "user.attribute": "foo",
- "access.token.claim": "true",
- "claim.name": "resource_access.${client_id}.roles",
- "jsonType.label": "String",
- "multivalued": "true"
- }
- }
- ]
- },
- {
- "id": "5457f36a-e484-4a2b-973e-ac53a3e5c263",
- "name": "phone",
- "description": "OpenID Connect built-in scope: phone",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${phoneScopeConsentText}"
- },
- "protocolMappers": [
- {
- "id": "6355b8ff-30a4-4788-8052-2660de265bf5",
- "name": "phone number verified",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "phoneNumberVerified",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "phone_number_verified",
- "jsonType.label": "boolean"
- }
- },
- {
- "id": "b790aeec-2188-4fc7-9b1f-4be165ca10f8",
- "name": "phone number",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "phoneNumber",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "phone_number",
- "jsonType.label": "String"
- }
- }
- ]
- },
- {
- "id": "34cb837f-3790-4127-8ba7-9dd2008e1ff2",
- "name": "role_list",
- "description": "SAML role list",
- "protocol": "saml",
- "attributes": {
- "consent.screen.text": "${samlRoleListScopeConsentText}",
- "display.on.consent.screen": "true"
- },
- "protocolMappers": [
- {
- "id": "bada63ea-1044-4f44-8d01-78ceae0635b4",
- "name": "role list",
- "protocol": "saml",
- "protocolMapper": "saml-role-list-mapper",
- "consentRequired": false,
- "config": {
- "single": "false",
- "attribute.nameformat": "Basic",
- "attribute.name": "Role"
- }
- }
- ]
- },
- {
- "id": "e4bee0f6-1318-4a28-bfe7-1d4fde10aec7",
- "name": "acr",
- "description": "OpenID Connect scope for add acr (authentication context class reference) to the token",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "false",
- "display.on.consent.screen": "false"
- },
- "protocolMappers": [
- {
- "id": "7ee0a034-aa71-40f1-9231-88826c04d236",
- "name": "acr loa level",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-acr-mapper",
- "consentRequired": false,
- "config": {
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true"
- }
- }
- ]
- },
- {
- "id": "1e02ccb4-2472-4101-8e8b-bd52c636e6f7",
- "name": "web-origins",
- "description": "OpenID Connect scope for add allowed web origins to the access token",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "false",
- "display.on.consent.screen": "false",
- "consent.screen.text": ""
- },
- "protocolMappers": [
- {
- "id": "d8c4868b-b5a1-4885-a2d7-745de6055b29",
- "name": "allowed web origins",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-allowed-origins-mapper",
- "consentRequired": false,
- "config": {}
- }
- ]
- },
- {
- "id": "dcd2084c-bdc5-4ee8-b944-c0e8f29b95bf",
- "name": "profile",
- "description": "OpenID Connect built-in scope: profile",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${profileScopeConsentText}"
- },
- "protocolMappers": [
- {
- "id": "fb3564b3-efe2-44ca-832f-0743627327f1",
- "name": "zoneinfo",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "zoneinfo",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "zoneinfo",
- "jsonType.label": "String"
- }
- },
- {
- "id": "00762359-6f62-4147-bbdc-b038ddbd0764",
- "name": "given name",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "firstName",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "given_name",
- "jsonType.label": "String"
- }
- },
- {
- "id": "d3b84b3e-8d61-4f45-96df-537a0d4fac0b",
- "name": "picture",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "picture",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "picture",
- "jsonType.label": "String"
- }
- },
- {
- "id": "dab64ee2-6e2d-4c89-a279-ef64678a4411",
- "name": "nickname",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "nickname",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "nickname",
- "jsonType.label": "String"
- }
- },
- {
- "id": "cbc013f6-76f5-4de6-9c75-7e7d8e0e5247",
- "name": "username",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "username",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "preferred_username",
- "jsonType.label": "String"
- }
- },
- {
- "id": "93f40b5a-aae2-42a7-b673-a2fb368e1e34",
- "name": "full name",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-full-name-mapper",
- "consentRequired": false,
- "config": {
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true"
- }
- },
- {
- "id": "4e98eb77-64d1-41d4-bf27-8a9bb0c4f8b3",
- "name": "family name",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "lastName",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "family_name",
- "jsonType.label": "String"
- }
- },
- {
- "id": "f4e6db9a-4ae6-47ca-96da-db117100cea3",
- "name": "profile",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "profile",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "profile",
- "jsonType.label": "String"
- }
- },
- {
- "id": "cdde6ba9-c866-42b3-b1f3-1eb3dd30b680",
- "name": "locale",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "locale",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "locale",
- "jsonType.label": "String"
- }
- },
- {
- "id": "1195e372-8317-4258-9708-c3f34f7277d8",
- "name": "website",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "website",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "website",
- "jsonType.label": "String"
- }
- },
- {
- "id": "bc35bc42-43c1-4908-89b5-6ff4c39d28f1",
- "name": "middle name",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "middleName",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "middle_name",
- "jsonType.label": "String"
- }
- },
- {
- "id": "162be747-d54d-480e-860f-15bfa3103ddd",
- "name": "gender",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "gender",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "gender",
- "jsonType.label": "String"
- }
- },
- {
- "id": "a5048508-f27d-4f82-8891-ebb5f73ed999",
- "name": "updated at",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "updatedAt",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "updated_at",
- "jsonType.label": "long"
- }
- },
- {
- "id": "14386740-0c98-4be7-8596-54c4b06afe77",
- "name": "birthdate",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "birthdate",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "birthdate",
- "jsonType.label": "String"
- }
- }
- ]
- },
- {
- "id": "f2901b5e-8a99-4d1f-89dc-9a5cf6c02717",
- "name": "address",
- "description": "OpenID Connect built-in scope: address",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${addressScopeConsentText}"
- },
- "protocolMappers": [
- {
- "id": "0bcf4a6a-9953-495c-aa80-adf990726893",
- "name": "address",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-address-mapper",
- "consentRequired": false,
- "config": {
- "user.attribute.formatted": "formatted",
- "user.attribute.country": "country",
- "user.attribute.postal_code": "postal_code",
- "userinfo.token.claim": "true",
- "user.attribute.street": "street",
- "id.token.claim": "true",
- "user.attribute.region": "region",
- "access.token.claim": "true",
- "user.attribute.locality": "locality"
- }
- }
- ]
- },
- {
- "id": "495e0679-d71f-4b9f-b0c6-a125ea01d5e8",
- "name": "email",
- "description": "OpenID Connect built-in scope: email",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "true",
- "consent.screen.text": "${emailScopeConsentText}"
- },
- "protocolMappers": [
- {
- "id": "233fd2cd-7398-41f4-9866-7044490617f0",
- "name": "email verified",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "emailVerified",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "email_verified",
- "jsonType.label": "boolean"
- }
- },
- {
- "id": "a717da78-c9a8-4db8-9a0f-28033aacb345",
- "name": "email",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "email",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "email",
- "jsonType.label": "String"
- }
- }
- ]
- },
- {
- "id": "5e014983-7c19-47e8-877c-0eb45231c6e3",
- "name": "microprofile-jwt",
- "description": "Microprofile - JWT built-in scope",
- "protocol": "openid-connect",
- "attributes": {
- "include.in.token.scope": "true",
- "display.on.consent.screen": "false"
- },
- "protocolMappers": [
- {
- "id": "6848d162-c7da-427d-a8cb-1b740b6c5d25",
- "name": "upn",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-property-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "username",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "upn",
- "jsonType.label": "String"
- }
- },
- {
- "id": "e57c29b9-2506-4a37-bd39-307ec0faa1f0",
- "name": "groups",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-realm-role-mapper",
- "consentRequired": false,
- "config": {
- "multivalued": "true",
- "userinfo.token.claim": "true",
- "user.attribute": "foo",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "groups",
- "jsonType.label": "String"
- }
- }
- ]
- },
- {
- "id": "0c6e1add-2c19-4f0e-8ad6-03f4c2fd9086",
- "name": "offline_access",
- "description": "OpenID Connect built-in scope: offline_access",
- "protocol": "openid-connect",
- "attributes": {
- "consent.screen.text": "${offlineAccessScopeConsentText}",
- "display.on.consent.screen": "true"
- }
- }
- ],
- "defaultDefaultClientScopes": [
- "web-origins",
- "roles",
- "role_list",
- "email",
- "profile",
- "acr"
- ],
- "defaultOptionalClientScopes": [
- "offline_access",
- "phone",
- "microprofile-jwt",
- "address"
- ],
- "browserSecurityHeaders": {
- "contentSecurityPolicyReportOnly": "",
- "xContentTypeOptions": "nosniff",
- "xRobotsTag": "none",
- "xFrameOptions": "SAMEORIGIN",
- "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
- "xXSSProtection": "1; mode=block",
- "strictTransportSecurity": "max-age=31536000; includeSubDomains"
- },
- "smtpServer": {},
- "eventsEnabled": false,
- "eventsListeners": [
- "jboss-logging"
- ],
- "enabledEventTypes": [],
- "adminEventsEnabled": false,
- "adminEventsDetailsEnabled": false,
- "identityProviders": [],
- "identityProviderMappers": [],
- "components": {
- "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
- {
- "id": "644a1f9f-59d4-4b9c-93b7-b7891b8326d8",
- "name": "Trusted Hosts",
- "providerId": "trusted-hosts",
- "subType": "anonymous",
- "subComponents": {},
- "config": {
- "host-sending-registration-request-must-match": [
- "true"
- ],
- "client-uris-must-match": [
- "true"
- ]
- }
- },
- {
- "id": "676bb8f2-76aa-445c-a6ea-a65eebc3f891",
- "name": "Allowed Protocol Mapper Types",
- "providerId": "allowed-protocol-mappers",
- "subType": "authenticated",
- "subComponents": {},
- "config": {
- "allowed-protocol-mapper-types": [
- "saml-user-attribute-mapper",
- "saml-role-list-mapper",
- "oidc-address-mapper",
- "oidc-full-name-mapper",
- "oidc-usermodel-attribute-mapper",
- "oidc-usermodel-property-mapper",
- "oidc-sha256-pairwise-sub-mapper",
- "saml-user-property-mapper"
- ]
- }
- },
- {
- "id": "909a97d0-5ae1-4032-826e-f141e94fd70b",
- "name": "Max Clients Limit",
- "providerId": "max-clients",
- "subType": "anonymous",
- "subComponents": {},
- "config": {
- "max-clients": [
- "200"
- ]
- }
- },
- {
- "id": "5c56d234-38d9-48d2-90c2-3a30fcd66420",
- "name": "Consent Required",
- "providerId": "consent-required",
- "subType": "anonymous",
- "subComponents": {},
- "config": {}
- },
- {
- "id": "57d64f65-738e-4e85-b7a6-2ea8d67d66e5",
- "name": "Full Scope Disabled",
- "providerId": "scope",
- "subType": "anonymous",
- "subComponents": {},
- "config": {}
- },
- {
- "id": "ca30784b-25f5-4b4a-af95-5e363b8781dc",
- "name": "Allowed Protocol Mapper Types",
- "providerId": "allowed-protocol-mappers",
- "subType": "anonymous",
- "subComponents": {},
- "config": {
- "allowed-protocol-mapper-types": [
- "oidc-sha256-pairwise-sub-mapper",
- "oidc-usermodel-property-mapper",
- "saml-role-list-mapper",
- "oidc-full-name-mapper",
- "oidc-usermodel-attribute-mapper",
- "saml-user-attribute-mapper",
- "saml-user-property-mapper",
- "oidc-address-mapper"
- ]
- }
- },
- {
- "id": "0fc8702b-2718-469c-8e83-eb26f806cf4d",
- "name": "Allowed Client Scopes",
- "providerId": "allowed-client-templates",
- "subType": "authenticated",
- "subComponents": {},
- "config": {
- "allow-default-scopes": [
- "true"
- ]
- }
- },
- {
- "id": "1c68f5ad-2a9e-4394-936e-2d436e46f715",
- "name": "Allowed Client Scopes",
- "providerId": "allowed-client-templates",
- "subType": "anonymous",
- "subComponents": {},
- "config": {
- "allow-default-scopes": [
- "true"
- ]
- }
- }
- ],
- "org.keycloak.userprofile.UserProfileProvider": [
- {
- "id": "fe6c92b2-942e-4f85-ad9f-76aaf6878f8e",
- "providerId": "declarative-user-profile",
- "subComponents": {},
- "config": {}
- }
- ],
- "org.keycloak.keys.KeyProvider": [
- {
- "id": "e37b3ccb-ec33-4063-b67e-5e9c9ac1eb1a",
- "name": "rsa-generated",
- "providerId": "rsa-generated",
- "subComponents": {},
- "config": {
- "priority": [
- "100"
- ]
- }
- },
- {
- "id": "bb77c545-a449-44ec-997c-7efd1647ca5c",
- "name": "hmac-generated",
- "providerId": "hmac-generated",
- "subComponents": {},
- "config": {
- "priority": [
- "100"
- ],
- "algorithm": [
- "HS256"
- ]
- }
- },
- {
- "id": "ceaedfda-cd03-45fb-a617-59aeb0f7bf62",
- "name": "rsa-enc-generated",
- "providerId": "rsa-enc-generated",
- "subComponents": {},
- "config": {
- "priority": [
- "100"
- ],
- "algorithm": [
- "RSA-OAEP"
- ]
- }
- },
- {
- "id": "f98ffdb9-eb0d-4dd0-baac-4746556595e5",
- "name": "aes-generated",
- "providerId": "aes-generated",
- "subComponents": {},
- "config": {
- "priority": [
- "100"
- ]
- }
- }
- ]
- },
- "internationalizationEnabled": false,
- "supportedLocales": [],
- "authenticationFlows": [
- {
- "id": "648c54b2-368d-4fd8-9870-4305fbe36ac6",
- "alias": "Account verification options",
- "description": "Method with which to verity the existing account",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "idp-email-verification",
- "authenticatorFlow": false,
- "requirement": "ALTERNATIVE",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "ALTERNATIVE",
- "priority": 20,
- "autheticatorFlow": true,
- "flowAlias": "Verify Existing Account by Re-authentication",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "4ed8d1d5-5e7d-46f7-9987-8a176b8f781c",
- "alias": "Authentication Options",
- "description": "Authentication options.",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "basic-auth",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "basic-auth-otp",
- "authenticatorFlow": false,
- "requirement": "DISABLED",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "auth-spnego",
- "authenticatorFlow": false,
- "requirement": "DISABLED",
- "priority": 30,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "efc6df06-3810-442f-8ccb-3c0c38fcbf10",
- "alias": "Browser - Conditional OTP",
- "description": "Flow to determine if the OTP is required for the authentication",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "conditional-user-configured",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "auth-otp-form",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "2bb67a19-0ee5-4a6e-b9f6-a4093dd52e7d",
- "alias": "Direct Grant - Conditional OTP",
- "description": "Flow to determine if the OTP is required for the authentication",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "conditional-user-configured",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "direct-grant-validate-otp",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "c8acb54c-be4f-4dde-8a25-4d82c3705613",
- "alias": "First broker login - Conditional OTP",
- "description": "Flow to determine if the OTP is required for the authentication",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "conditional-user-configured",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "auth-otp-form",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "4211e5e4-990a-4f9b-8709-183c0aa31960",
- "alias": "Handle Existing Account",
- "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "idp-confirm-link",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": true,
- "flowAlias": "Account verification options",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "782b0454-2aeb-42a0-981b-a986866137fb",
- "alias": "Reset - Conditional OTP",
- "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "conditional-user-configured",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "reset-otp",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "106e526a-3f6a-42d3-990f-8f14fb49d8ef",
- "alias": "User creation or linking",
- "description": "Flow for the existing/non-existing user alternatives",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticatorConfig": "create unique user config",
- "authenticator": "idp-create-user-if-unique",
- "authenticatorFlow": false,
- "requirement": "ALTERNATIVE",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "ALTERNATIVE",
- "priority": 20,
- "autheticatorFlow": true,
- "flowAlias": "Handle Existing Account",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "4e5df2eb-5fc5-44ef-97fa-35f1e7c01aba",
- "alias": "Verify Existing Account by Re-authentication",
- "description": "Reauthentication of existing account",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "idp-username-password-form",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "CONDITIONAL",
- "priority": 20,
- "autheticatorFlow": true,
- "flowAlias": "First broker login - Conditional OTP",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "ba589416-fbba-4bed-9539-7270b2872371",
- "alias": "browser",
- "description": "browser based authentication",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "auth-cookie",
- "authenticatorFlow": false,
- "requirement": "ALTERNATIVE",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "auth-spnego",
- "authenticatorFlow": false,
- "requirement": "DISABLED",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "identity-provider-redirector",
- "authenticatorFlow": false,
- "requirement": "ALTERNATIVE",
- "priority": 25,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "ALTERNATIVE",
- "priority": 30,
- "autheticatorFlow": true,
- "flowAlias": "forms",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "06728976-310c-4c35-b352-a80dbb690749",
- "alias": "clients",
- "description": "Base authentication for clients",
- "providerId": "client-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "client-secret",
- "authenticatorFlow": false,
- "requirement": "ALTERNATIVE",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "client-jwt",
- "authenticatorFlow": false,
- "requirement": "ALTERNATIVE",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "client-secret-jwt",
- "authenticatorFlow": false,
- "requirement": "ALTERNATIVE",
- "priority": 30,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "client-x509",
- "authenticatorFlow": false,
- "requirement": "ALTERNATIVE",
- "priority": 40,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "12b29bbd-fb22-4d49-9dcc-c3ff17af1e3a",
- "alias": "direct grant",
- "description": "OpenID Connect Resource Owner Grant",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "direct-grant-validate-username",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "direct-grant-validate-password",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "CONDITIONAL",
- "priority": 30,
- "autheticatorFlow": true,
- "flowAlias": "Direct Grant - Conditional OTP",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "8c46aa41-e58c-4cf2-a092-0147fa7d640a",
- "alias": "docker auth",
- "description": "Used by Docker clients to authenticate against the IDP",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "docker-http-basic-authenticator",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "109a3a37-28ad-4b85-9b69-adbfa9aef87d",
- "alias": "first broker login",
- "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticatorConfig": "review profile config",
- "authenticator": "idp-review-profile",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": true,
- "flowAlias": "User creation or linking",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "56ce6e93-74d8-421f-ad0d-8bade834ef98",
- "alias": "forms",
- "description": "Username, password, otp and other auth forms.",
- "providerId": "basic-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "auth-username-password-form",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "CONDITIONAL",
- "priority": 20,
- "autheticatorFlow": true,
- "flowAlias": "Browser - Conditional OTP",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "7adbab75-4795-46ed-b26d-6434f9c32363",
- "alias": "http challenge",
- "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "no-cookie-redirect",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": true,
- "flowAlias": "Authentication Options",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "4b3c8b6b-d98c-42be-90b3-5846873ec82a",
- "alias": "registration",
- "description": "registration flow",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "registration-page-form",
- "authenticatorFlow": true,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": true,
- "flowAlias": "registration form",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "fc64bd4e-7e99-4765-892d-93e5195b3cdf",
- "alias": "registration form",
- "description": "registration form",
- "providerId": "form-flow",
- "topLevel": false,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "registration-user-creation",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "registration-profile-action",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 40,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "registration-password-action",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 50,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "registration-recaptcha-action",
- "authenticatorFlow": false,
- "requirement": "DISABLED",
- "priority": 60,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "c2821b19-4cb5-4090-b18b-0764f6ae4d92",
- "alias": "reset credentials",
- "description": "Reset credentials for a user if they forgot their password or something",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "reset-credentials-choose-user",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "reset-credential-email",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 20,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticator": "reset-password",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 30,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- },
- {
- "authenticatorFlow": true,
- "requirement": "CONDITIONAL",
- "priority": 40,
- "autheticatorFlow": true,
- "flowAlias": "Reset - Conditional OTP",
- "userSetupAllowed": false
- }
- ]
- },
- {
- "id": "4970610f-5cdf-45d6-9c4c-d17c6d8356a1",
- "alias": "saml ecp",
- "description": "SAML ECP Profile Authentication Flow",
- "providerId": "basic-flow",
- "topLevel": true,
- "builtIn": true,
- "authenticationExecutions": [
- {
- "authenticator": "http-basic-authenticator",
- "authenticatorFlow": false,
- "requirement": "REQUIRED",
- "priority": 10,
- "autheticatorFlow": false,
- "userSetupAllowed": false
- }
- ]
- }
- ],
- "authenticatorConfig": [
- {
- "id": "54a6bee1-851b-466c-b3ae-02ee129bb4ac",
- "alias": "create unique user config",
- "config": {
- "require.password.update.after.registration": "false"
- }
- },
- {
- "id": "52e91497-d0b9-4aa3-af42-6f0539ef48b3",
- "alias": "review profile config",
- "config": {
- "update.profile.on.first.login": "missing"
- }
- }
- ],
- "requiredActions": [
- {
- "alias": "CONFIGURE_TOTP",
- "name": "Configure OTP",
- "providerId": "CONFIGURE_TOTP",
- "enabled": true,
- "defaultAction": false,
- "priority": 10,
- "config": {}
- },
- {
- "alias": "terms_and_conditions",
- "name": "Terms and Conditions",
- "providerId": "terms_and_conditions",
- "enabled": false,
- "defaultAction": false,
- "priority": 20,
- "config": {}
- },
- {
- "alias": "UPDATE_PASSWORD",
- "name": "Update Password",
- "providerId": "UPDATE_PASSWORD",
- "enabled": true,
- "defaultAction": false,
- "priority": 30,
- "config": {}
- },
- {
- "alias": "UPDATE_PROFILE",
- "name": "Update Profile",
- "providerId": "UPDATE_PROFILE",
- "enabled": true,
- "defaultAction": false,
- "priority": 40,
- "config": {}
- },
- {
- "alias": "VERIFY_EMAIL",
- "name": "Verify Email",
- "providerId": "VERIFY_EMAIL",
- "enabled": true,
- "defaultAction": false,
- "priority": 50,
- "config": {}
- },
- {
- "alias": "delete_account",
- "name": "Delete Account",
- "providerId": "delete_account",
- "enabled": false,
- "defaultAction": false,
- "priority": 60,
- "config": {}
- },
- {
- "alias": "webauthn-register",
- "name": "Webauthn Register",
- "providerId": "webauthn-register",
- "enabled": true,
- "defaultAction": false,
- "priority": 70,
- "config": {}
- },
- {
- "alias": "webauthn-register-passwordless",
- "name": "Webauthn Register Passwordless",
- "providerId": "webauthn-register-passwordless",
- "enabled": true,
- "defaultAction": false,
- "priority": 80,
- "config": {}
- },
- {
- "alias": "update_user_locale",
- "name": "Update User Locale",
- "providerId": "update_user_locale",
- "enabled": true,
- "defaultAction": false,
- "priority": 1000,
- "config": {}
- }
- ],
- "browserFlow": "browser",
- "registrationFlow": "registration",
- "directGrantFlow": "direct grant",
- "resetCredentialsFlow": "reset credentials",
- "clientAuthenticationFlow": "clients",
- "dockerAuthenticationFlow": "docker auth",
- "attributes": {
- "cibaBackchannelTokenDeliveryMode": "poll",
- "cibaAuthRequestedUserHint": "login_hint",
- "clientOfflineSessionMaxLifespan": "0",
- "oauth2DevicePollingInterval": "5",
- "clientSessionIdleTimeout": "0",
- "clientOfflineSessionIdleTimeout": "0",
- "cibaInterval": "5",
- "cibaExpiresIn": "120",
- "oauth2DeviceCodeLifespan": "600",
- "parRequestUriLifespan": "60",
- "clientSessionMaxLifespan": "0",
- "frontendUrl": "",
- "acr.loa.map": "[]"
- },
- "keycloakVersion": "19.0.3",
- "userManagedAccessAllowed": false,
- "clientProfiles": {
- "profiles": []
- },
- "clientPolicies": {
- "policies": []
- }
-}
diff --git a/priv/wg0.client.conf b/priv/wg0.client.conf
deleted file mode 100644
index f5b469368..000000000
--- a/priv/wg0.client.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-# This config corresponds to the wireguard-client device
-# created when the DB is bootstrapped from apps/fz_http/priv/repo/seeds.exs.
-[Interface]
-Address = 100.64.100.1/32,fd00::6/128
-DNS = 127.0.0.11
-PrivateKey = UJ3WN7k8mnRTj33BAoiA1lw0ag24oB7NsTg01MaeNUI=
-MTU = 1280
-
-[Peer]
-PublicKey = is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=
-PresharedKey = C+Tte1echarIObr6rq+nFeYQ1QO5xo5N29ygDjMlpS8=
-PersistentKeepalive = 25
-AllowedIPs = 0.0.0.0/0,::/0
-Endpoint = firezone:51820
diff --git a/priv/wg_dev_private_key b/priv/wg_dev_private_key
deleted file mode 100644
index 3721eb35c..000000000
--- a/priv/wg_dev_private_key
+++ /dev/null
@@ -1 +0,0 @@
-wIq2WRxCctZAt9WB642jTTl/HFys9Ntht033s/0YKXI=