Make all tests pass

I removed some of VPN/Wall settings (they are irrelevant once we move out gateway) along with port-based rules conditions (since we are moving to userspace wg).
This commit is contained in:
Andrew Dryga
2023-04-04 09:41:12 -06:00
parent c5615060b4
commit 7fe685d072
408 changed files with 2200 additions and 2464 deletions

View File

@@ -0,0 +1,10 @@
[
import_deps: [
:ecto,
:plug
],
inputs: [
"*.{heex,ex,exs}",
"{lib,test,priv}/**/*.{heex,ex,exs}"
]
]

39
apps/domain/.gitignore vendored Normal file
View File

@@ -0,0 +1,39 @@
# macOS cruft
.DS_Store
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
cloudfire-*.tar
# If NPM crashes, it generates a log, let's ignore it too.
npm-debug.log
# The directory NPM downloads your dependencies sources to.
/assets/node_modules/
/assets/lib/node_modules/
/assets/bin/
# Since we are building assets from assets/,
# we ignore priv/static. You may want to comment
# this depending on your deployment strategy.
/priv/static/dist/

3
apps/domain/README.md Normal file
View File

@@ -0,0 +1,3 @@
# Domain
Phoenix app for managing Firezone.

View File

@@ -0,0 +1,5 @@
{
"skip_files": [
"test"
]
}

View File

@@ -1,84 +0,0 @@
defmodule FzHttp.Application do
use Application
def start(_type, _args) do
supervision_tree_mode = FzHttp.Config.fetch_env!(:fz_http, :supervision_tree_mode)
result =
supervision_tree_mode
|> children()
|> Supervisor.start_link(strategy: :one_for_one, name: __MODULE__.Supervisor)
:ok = after_start()
result
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
FzHttpWeb.Endpoint.config_change(changed, removed)
:ok
end
# XXX: get rid off this
defp children(:full) do
[
# Infrastructure services
FzHttp.Repo,
FzHttp.Vault,
{Phoenix.PubSub, name: FzHttp.PubSub},
{FzHttp.Notifications, name: FzHttp.Notifications},
FzHttpWeb.Presence,
# Application
{Postgrex.Notifications, [name: FzHttp.Repo.Notifications] ++ FzHttp.Repo.config()},
FzHttp.Repo.Notifier,
FzHttp.Server,
FzHttp.VpnSessionScheduler,
FzHttp.Auth,
FzHttpWeb.Endpoint,
# Observability
FzHttp.ConnectivityChecks,
FzHttp.Telemetry
]
end
defp children(:test) do
[
# Infrastructure services
FzHttp.Repo,
FzHttp.Vault,
{Phoenix.PubSub, name: FzHttp.PubSub},
{FzHttp.Notifications, name: FzHttp.Notifications},
FzHttpWeb.Presence,
# Application
FzHttp.Server,
FzHttp.Auth,
FzHttpWeb.Endpoint,
# Observability
FzHttp.ConnectivityChecks,
FzHttp.Telemetry
]
end
defp children(:database) do
[
FzHttp.Repo,
FzHttp.Vault
]
end
if Mix.env() == :prod do
defp after_start do
FzHttp.Config.validate_runtime_config!()
end
else
defp after_start do
:ok
end
end
end

View File

@@ -1,7 +0,0 @@
defmodule FzHttp.Encrypted.Binary do
@moduledoc """
Configures how to encrpyt Binaries to the DB.
"""
use Cloak.Ecto.Binary, vault: FzHttp.Vault
end

View File

@@ -1,7 +0,0 @@
defmodule FzHttp.Encrypted.Map do
@moduledoc """
Configures how to encrpyt Maps to the DB.
"""
use Cloak.Ecto.Map, vault: FzHttp.Vault
end

View File

@@ -1,39 +0,0 @@
defmodule FzHttp.Repo.Notifier do
@moduledoc """
Listens for Repo notifications and trigger events based on data changes.
"""
use GenServer
alias FzHttp.Events
alias FzHttp.Repo
@impl GenServer
def init(state) do
for subject <- ~w(devices rules users)a do
{:ok, _ref} = Postgrex.Notifications.listen(Repo.Notifications, "#{subject}_changed")
end
{:ok, state}
end
@impl GenServer
def handle_info({:notification, _pid, _ref, event, payload}, _state) do
subject = String.split(event, "_") |> List.first()
data = Jason.decode!(payload, keys: :atoms)
handle_event(subject, data)
{:noreply, :event_handled}
end
def start_link(opts \\ []), do: GenServer.start_link(__MODULE__, opts)
def handle_event(subject, %{op: "INSERT"} = data) do
Events.add(subject, data.row)
end
def handle_event(subject, %{op: "DELETE"} = data) do
Events.delete(subject, data.row)
end
end

View File

@@ -1,14 +0,0 @@
defmodule FzHttp.Rules.Rule do
use FzHttp, :schema
schema "rules" do
field :action, Ecto.Enum, values: [:drop, :accept], default: :drop
field :destination, FzHttp.Types.INET
field :port_type, Ecto.Enum, values: [:tcp, :udp]
field :port_range, FzHttp.Types.Int4Range
belongs_to :user, FzHttp.Users.User
timestamps()
end
end

View File

@@ -1,41 +0,0 @@
defmodule FzHttp.Server do
@moduledoc """
Functions for other processes to interact with the FzHttp application
"""
use GenServer
alias FzHttp.{Devices, Devices.StatsUpdater, Rules, Users}
def start_link(_) do
# We're not storing state, simply providing an API
GenServer.start_link(__MODULE__, nil, name: {:global, :fz_http_server})
end
@impl GenServer
def init(state) do
{:ok, state}
end
@impl GenServer
def handle_call(:load_peers, _from, state) do
reply = {:ok, Devices.to_peer_list()}
{:reply, reply, state}
end
@impl GenServer
def handle_call(:load_settings, _from, state) do
reply =
{:ok,
%{
users: Users.as_settings(),
devices: Devices.as_settings(),
rules: Rules.as_settings()
}}
{:reply, reply, state}
end
@impl GenServer
def handle_call({:update_device_stats, stats}, _from, state) do
{:reply, StatsUpdater.update(stats), state}
end
end

View File

@@ -1,21 +0,0 @@
defimpl String.Chars, for: Postgrex.INET do
def to_string(%Postgrex.INET{} = inet), do: FzHttp.Types.INET.to_string(inet)
end
defimpl Phoenix.HTML.Safe, for: Postgrex.INET do
def to_iodata(%Postgrex.INET{} = inet), do: FzHttp.Types.INET.to_string(inet)
end
defimpl Jason.Encoder, for: Postgrex.INET do
def encode(%Postgrex.INET{} = struct, opts) do
Jason.Encode.string("#{struct}", opts)
end
end
defimpl String.Chars, for: FzHttp.Types.IPPort do
def to_string(%FzHttp.Types.IPPort{} = ip_port), do: FzHttp.Types.IPPort.to_string(ip_port)
end
defimpl Phoenix.HTML.Safe, for: FzHttp.Types.IPPort do
def to_iodata(%FzHttp.Types.IPPort{} = ip_port), do: FzHttp.Types.IPPort.to_string(ip_port)
end

View File

@@ -1,7 +0,0 @@
defmodule FzHttp.Vault do
@moduledoc """
Manages encrypted DB fields.
"""
use Cloak.Vault, otp_app: :fz_http
end

View File

@@ -1,27 +0,0 @@
defmodule FzHttp.VpnSessionScheduler do
@moduledoc """
Checks for VPN sessions to expire.
"""
use GenServer
alias FzHttp.Events
# 1 minute
@interval 60 * 1_000
def start_link(_) do
GenServer.start_link(__MODULE__, %{})
end
@impl GenServer
def init(state) do
:timer.send_interval(@interval, :perform)
{:ok, state}
end
@impl GenServer
def handle_info(:perform, state) do
Events.set_config()
{:noreply, state}
end
end

View File

@@ -1,4 +1,9 @@
defmodule FzHttp do
defmodule Domain do
@moduledoc """
This module provides a common interface for all the domain modules,
making sure our code structure is consistent and predictable.
"""
def schema do
quote do
use Ecto.Schema
@@ -15,7 +20,7 @@ defmodule FzHttp do
def changeset do
quote do
import Ecto.Changeset
import FzHttp.Validator
import Domain.Validator
end
end

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.ApiTokens do
alias FzHttp.{Repo, Validator, Auth}
alias FzHttp.Users
alias FzHttp.ApiTokens.Authorizer
alias FzHttp.ApiTokens.ApiToken
defmodule Domain.ApiTokens do
alias Domain.{Repo, Validator, Auth}
alias Domain.Users
alias Domain.ApiTokens.Authorizer
alias Domain.ApiTokens.ApiToken
def count_by_user_id(user_id) do
ApiToken.Query.by_user_id(user_id)
@@ -112,7 +112,7 @@ defmodule FzHttp.ApiTokens do
changeset = ApiToken.Changeset.create_changeset(user, attrs, max: count_by_user_id)
with {:ok, api_token} <- Repo.insert(changeset) do
FzHttp.Telemetry.create_api_token()
Domain.Telemetry.create_api_token()
{:ok, api_token}
end
end

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.ApiTokens.ApiToken do
use FzHttp, :schema
defmodule Domain.ApiTokens.ApiToken do
use Domain, :schema
schema "api_tokens" do
field :expires_at, :utc_datetime_usec
@@ -7,7 +7,7 @@ defmodule FzHttp.ApiTokens.ApiToken do
# Developer-friendly way to set expires_at
field :expires_in, :integer, virtual: true, default: 30
belongs_to :user, FzHttp.Users.User
belongs_to :user, Domain.Users.User
timestamps(updated_at: false)
end

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.ApiTokens.ApiToken.Changeset do
use FzHttp, :changeset
alias FzHttp.ApiTokens.ApiToken
defmodule Domain.ApiTokens.ApiToken.Changeset do
use Domain, :changeset
alias Domain.ApiTokens.ApiToken
@max_per_user 25

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.ApiTokens.ApiToken.Query do
use FzHttp, :query
defmodule Domain.ApiTokens.ApiToken.Query do
use Domain, :query
def all do
from(api_tokens in FzHttp.ApiTokens.ApiToken, as: :api_tokens)
from(api_tokens in Domain.ApiTokens.ApiToken, as: :api_tokens)
end
def by_id(queryable \\ all(), id) do

View File

@@ -1,11 +1,11 @@
defmodule FzHttp.ApiTokens.Authorizer do
use FzHttp.Auth.Authorizer
alias FzHttp.ApiTokens.ApiToken
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 FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def list_permissions_for_role(:admin) do
[
manage_own_api_tokens_permission(),
@@ -17,7 +17,7 @@ defmodule FzHttp.ApiTokens.Authorizer do
[]
end
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def for_subject(queryable, %Subject{} = subject) when is_user(subject) do
cond do
has_permission?(subject, manage_api_tokens_permission()) ->

View File

@@ -0,0 +1,39 @@
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
end
# TODO: when app starts for migrations set env to disable connectivity checks and telemetry
def children do
[
# Infrastructure services
Domain.Repo,
Domain.Vault,
{Phoenix.PubSub, name: Domain.PubSub},
# Application
{Domain.Notifications, name: Domain.Notifications},
Domain.Auth,
# Observability
Domain.ConnectivityChecks,
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

View File

@@ -1,9 +1,9 @@
defmodule FzHttp.Auth do
defmodule Domain.Auth do
use Supervisor
alias FzHttp.Repo
alias FzHttp.Config
alias FzHttp.{Users, ApiTokens}
alias FzHttp.Auth.{Subject, Context, Permission, Roles}
alias Domain.Repo
alias Domain.Config
alias Domain.{Users, ApiTokens}
alias Domain.Auth.{Subject, Context, Permission, Roles}
def start_link(opts) do
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
@@ -11,9 +11,9 @@ defmodule FzHttp.Auth do
def init(_opts) do
children = [
FzHttp.Auth.SAML.StartProxy,
{DynamicSupervisor, name: FzHttp.RefresherSupervisor, strategy: :one_for_one},
FzHttp.Auth.OIDC.RefreshManager
Domain.Auth.SAML.StartProxy,
{DynamicSupervisor, name: Domain.RefresherSupervisor, strategy: :one_for_one},
Domain.Auth.OIDC.RefreshManager
]
Supervisor.init(children, strategy: :one_for_one)
@@ -84,7 +84,7 @@ defmodule FzHttp.Auth do
if provider.redirect_uri do
provider.redirect_uri
else
external_url = FzHttp.Config.fetch_env!(:fz_http, :external_url)
external_url = Domain.Config.fetch_env!(:web, :external_url)
"#{external_url}auth/oidc/#{provider.id}/callback/"
end

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Auth.Authorizer do
defmodule Domain.Auth.Authorizer do
@moduledoc """
Business contexts use authorization modules to define permissions that are supported by the context
and expose them to the authorization system by implementing behaviour provided by this module.
@@ -6,11 +6,11 @@ defmodule FzHttp.Auth.Authorizer do
defmacro __using__(_opts) do
quote do
import FzHttp.Auth.Authorizer
import FzHttp.Auth, only: [has_permission?: 2]
alias FzHttp.Auth.Subject
import Domain.Auth.Authorizer
import Domain.Auth, only: [has_permission?: 2]
alias Domain.Auth.Subject
@behaviour FzHttp.Auth.Authorizer
@behaviour Domain.Auth.Authorizer
end
end
@@ -18,24 +18,24 @@ defmodule FzHttp.Auth.Authorizer do
Returns list of all permissions defined by implementation module,
which is used to simplify role management.
"""
@callback list_permissions_for_role(FzHttp.Auth.Role.name()) :: [FzHttp.Auth.Permission.t()]
@callback list_permissions_for_role(Domain.Auth.Role.name()) :: [Domain.Auth.Permission.t()]
@doc """
Optional helper which allows to filter queryable based on subject permissions.
"""
@callback for_subject(Ecto.Queryable.t(), FzHttp.Auth.Subject.t()) :: Ecto.Queryable.t()
@callback for_subject(Ecto.Queryable.t(), Domain.Auth.Subject.t()) :: Ecto.Queryable.t()
@optional_callbacks for_subject: 2
def build(resource, action) do
%FzHttp.Auth.Permission{resource: resource, action: action}
%Domain.Auth.Permission{resource: resource, action: action}
end
defguard is_user(subject)
when is_struct(subject, FzHttp.Auth.Subject) and
when is_struct(subject, Domain.Auth.Subject) and
elem(subject.actor, 0) == :user
defguard is_api_token(subject)
when is_struct(subject, FzHttp.Auth.Subject) and
when is_struct(subject, Domain.Auth.Subject) and
elem(subject.actor, 0) == :api_token
end

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Auth.Context do
defmodule Domain.Auth.Context do
@typedoc """
This structure represents an authentication context for a user or an API token.

View File

@@ -1,7 +1,7 @@
defmodule FzHttp.Auth.MFA do
alias FzHttp.{Repo, Validator}
alias FzHttp.Users
alias FzHttp.Auth.MFA.Method
defmodule Domain.Auth.MFA do
alias Domain.{Repo, Validator}
alias Domain.Users
alias Domain.Auth.MFA.Method
def count_users_with_mfa_enabled do
Method.Query.select_distinct_user_ids_count()

View File

@@ -1,15 +1,15 @@
defmodule FzHttp.Auth.MFA.Method do
use FzHttp, :schema
defmodule Domain.Auth.MFA.Method do
use Domain, :schema
schema "mfa_methods" do
field :name, :string
field :type, Ecto.Enum, values: [:totp, :native, :portable]
field :last_used_at, :utc_datetime_usec
field :payload, FzHttp.Encrypted.Map
field :payload, Domain.Encrypted.Map
field :code, :string, virtual: true
belongs_to :user, FzHttp.Users.User
belongs_to :user, Domain.Users.User
timestamps()
end

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.Auth.MFA.Method.Changeset do
use FzHttp, :changeset
alias FzHttp.Auth.MFA.Method
defmodule Domain.Auth.MFA.Method.Changeset do
use Domain, :changeset
alias Domain.Auth.MFA.Method
@create_fields [:name, :type, :payload, :code]
@@ -11,7 +11,7 @@ defmodule FzHttp.Auth.MFA.Method.Changeset do
|> validate_length(:name, min: 1, max: 255)
|> trim_change(:name)
|> unique_constraint(:name, name: :mfa_methods_user_id_name_index)
|> unsafe_validate_unique([:name, :user_id], FzHttp.Repo)
|> unsafe_validate_unique([:name, :user_id], Domain.Repo)
|> assoc_constraint(:user)
|> changeset()
end

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.Auth.MFA.Method.Query do
use FzHttp, :query
defmodule Domain.Auth.MFA.Method.Query do
use Domain, :query
def all do
from(users in FzHttp.Auth.MFA.Method, as: :methods)
from(users in Domain.Auth.MFA.Method, as: :methods)
end
def by_id(queryable \\ all(), id) do

View File

@@ -1,9 +1,9 @@
defmodule FzHttp.Auth.OIDC do
defmodule Domain.Auth.OIDC do
@moduledoc """
The OIDC context.
"""
import Ecto.Query, warn: false
alias FzHttp.{Auth.OIDC.Connection, Repo, Users.User}
alias Domain.{Auth.OIDC.Connection, Repo, Users.User}
def list_connections(%User{id: id}) do
Repo.all(from(Connection, where: [user_id: ^id]))

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.Auth.OIDC.Connection do
use FzHttp, :schema
defmodule Domain.Auth.OIDC.Connection do
use Domain, :schema
schema "oidc_connections" do
field :provider, :string
@@ -7,7 +7,7 @@ defmodule FzHttp.Auth.OIDC.Connection do
field :refresh_token, :string
field :refreshed_at, :utc_datetime_usec
belongs_to :user, FzHttp.Users.User
belongs_to :user, Domain.Users.User
timestamps()
end

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.Auth.OIDC.Connection.Changeset do
use FzHttp, :changeset
defmodule Domain.Auth.OIDC.Connection.Changeset do
use Domain, :changeset
@fields ~w[provider refresh_token refreshed_at refresh_response]a
@required_fields ~w[provider refresh_token]a

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.Auth.OIDC.Connection.Query do
use FzHttp, :query
defmodule Domain.Auth.OIDC.Connection.Query do
use Domain, :query
def all do
from(connection in FzHttp.Auth.OIDC.Connection, as: :connection)
from(connection in Domain.Auth.OIDC.Connection, as: :connection)
end
def by_id(queryable \\ all(), id) do

View File

@@ -1,11 +1,11 @@
defmodule FzHttp.Auth.OIDC.RefreshManager do
defmodule Domain.Auth.OIDC.RefreshManager do
@moduledoc """
Manager module for refreshing OIDC connections
"""
use GenServer, restart: :permanent
import Ecto.Query
alias FzHttp.{Repo, Users.User}
alias Domain.{Repo, Users.User}
# Refresh every 10 minutes -- Keycloak's ttl for refresh tokens
# is 30 minutes by default.
@@ -46,8 +46,8 @@ defmodule FzHttp.Auth.OIDC.RefreshManager do
delay_after_spawn = Enum.random(1..@max_delay_after_spawn) * 1000
DynamicSupervisor.start_child(
FzHttp.RefresherSupervisor,
{FzHttp.Auth.OIDC.Refresher, {id, delay_after_spawn}}
Domain.RefresherSupervisor,
{Domain.Auth.OIDC.Refresher, {id, delay_after_spawn}}
)
end
end

View File

@@ -1,9 +1,9 @@
defmodule FzHttp.Auth.OIDC.Refresher do
defmodule Domain.Auth.OIDC.Refresher do
@moduledoc """
Worker module for refreshing OIDC connections
"""
use GenServer, restart: :temporary
alias FzHttp.{Auth, Auth.OIDC, Auth.OIDC.Connection, Repo, Users}
alias Domain.{Auth, Auth.OIDC, Auth.OIDC.Connection, Repo, Users}
require Logger
def start_link(init_opts) do
@@ -60,6 +60,6 @@ defmodule FzHttp.Auth.OIDC.Refresher do
end
defp enabled? do
FzHttp.Config.fetch_config!(:disable_vpn_on_oidc_error)
Domain.Config.fetch_config!(:disable_vpn_on_oidc_error)
end
end

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Auth.Permission do
defmodule Domain.Auth.Permission do
@type resource :: module()
@typedoc """

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.Auth.Role do
alias FzHttp.Auth.Permission
defmodule Domain.Auth.Role do
alias Domain.Auth.Permission
@type name :: atom()

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.Auth.Roles do
alias FzHttp.Auth.Role
defmodule Domain.Auth.Roles do
alias Domain.Auth.Role
def list_roles do
[
@@ -10,12 +10,12 @@ defmodule FzHttp.Auth.Roles do
defp list_authorizers do
[
FzHttp.Config.Authorizer,
FzHttp.ApiTokens.Authorizer,
FzHttp.ConnectivityChecks.Authorizer,
FzHttp.Devices.Authorizer,
FzHttp.Rules.Authorizer,
FzHttp.Users.Authorizer
Domain.Config.Authorizer,
Domain.ApiTokens.Authorizer,
Domain.ConnectivityChecks.Authorizer,
Domain.Devices.Authorizer,
Domain.Rules.Authorizer,
Domain.Users.Authorizer
]
end

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Auth.SAML.StartProxy do
defmodule Domain.Auth.SAML.StartProxy do
@moduledoc """
This proxy starts Samly.Provider with proper configs
"""
@@ -20,7 +20,7 @@ defmodule FzHttp.Auth.SAML.StartProxy do
end
def set_service_provider(samly_configs) do
config = FzHttp.Config.fetch_env!(:fz_http, FzHttp.SAML)
config = Domain.Config.fetch_env!(:web, Web.SAML)
entity_id = Keyword.fetch!(config, :entity_id)
keyfile = Keyword.fetch!(config, :keyfile_path)
certfile = Keyword.fetch!(config, :certfile_path)
@@ -55,9 +55,9 @@ defmodule FzHttp.Auth.SAML.StartProxy do
Keyword.put(samly_configs, :identity_providers, identity_providers)
end
def refresh(providers \\ FzHttp.Config.fetch_config!(:saml_identity_providers)) do
def refresh(providers \\ Domain.Config.fetch_config!(:saml_identity_providers)) do
samly_configs =
FzHttp.Config.fetch_env!(:samly, Samly.Provider)
Domain.Config.fetch_env!(:samly, Samly.Provider)
|> set_service_provider()
|> set_identity_providers(providers)

View File

@@ -1,9 +1,9 @@
defmodule FzHttp.Auth.Subject do
alias FzHttp.Auth.{Permission, Context}
defmodule Domain.Auth.Subject do
alias Domain.Auth.{Permission, Context}
@type actor ::
{:user, %FzHttp.Users.User{}}
| {:api_token, %FzHttp.ApiTokens.ApiToken{}}
{:user, %Domain.Users.User{}}
| {:api_token, %Domain.ApiTokens.ApiToken{}}
| :system
@type permission :: Permission.t()

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.Config do
alias FzHttp.{Repo, Auth}
alias FzHttp.Config.Authorizer
alias FzHttp.Config.{Definition, Definitions, Validator, Errors, Fetcher}
alias FzHttp.Config.Configuration
defmodule Domain.Config do
alias Domain.{Repo, Auth}
alias Domain.Config.Authorizer
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)
@@ -59,7 +59,7 @@ defmodule FzHttp.Config do
end
defp maybe_fetch_db_config!(keys) when is_list(keys) do
if Enum.any?(keys, &(&1 in FzHttp.Config.Configuration.__schema__(:fields))) do
if Enum.any?(keys, &(&1 in Domain.Config.Configuration.__schema__(:fields))) do
fetch_db_config!()
else
%{}
@@ -67,7 +67,7 @@ defmodule FzHttp.Config do
end
defp maybe_fetch_db_config!(key) do
if key in FzHttp.Config.Configuration.__schema__(:fields) do
if key in Domain.Config.Configuration.__schema__(:fields) do
fetch_db_config!()
else
%{}
@@ -134,7 +134,7 @@ defmodule FzHttp.Config do
changeset = Configuration.Changeset.changeset(config, attrs)
with {:ok, config} <- Repo.update(changeset) do
FzHttp.Auth.SAML.StartProxy.refresh(config.saml_identity_providers)
Domain.Auth.SAML.StartProxy.refresh(config.saml_identity_providers)
{:ok, config}
end
end
@@ -176,13 +176,13 @@ defmodule FzHttp.Config do
if Mix.env() != :test do
defdelegate fetch_env!(app, key), to: Application
else
def put_env_override(app \\ :fz_http, key, value) do
def put_env_override(app \\ :domain, key, value) do
Process.put(pdict_key_function(app, key), value)
:ok
end
def put_system_env_override(key, value) when is_atom(key) do
Process.put({FzHttp.Config.Resolver, key}, {:env, value})
Process.put({Domain.Config.Resolver, key}, {:env, value})
:ok
end
@@ -199,7 +199,7 @@ defmodule FzHttp.Config do
application_env = Application.fetch_env!(app, key)
pdict_key_function(app, key)
|> FzHttp.Config.Resolver.fetch_process_env()
|> Domain.Config.Resolver.fetch_process_env()
|> case do
{:ok, override} ->
override

View File

@@ -1,10 +1,10 @@
defmodule FzHttp.Config.Authorizer do
use FzHttp.Auth.Authorizer
alias FzHttp.Config.Configuration
defmodule Domain.Config.Authorizer do
use Domain.Auth.Authorizer
alias Domain.Config.Configuration
def configure_permission, do: build(Configuration, :manage)
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def list_permissions_for_role(:admin) do
[
configure_permission()

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Config.Caster do
defmodule Domain.Config.Caster do
@moduledoc """
This module allows to cast values to a defined type.

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.Config.Configuration do
use FzHttp, :schema
alias FzHttp.Config.Logo
defmodule Domain.Config.Configuration do
use Domain, :schema
alias Domain.Config.Logo
schema "configurations" do
field :allow_unprivileged_device_management, :boolean
@@ -10,7 +10,7 @@ defmodule FzHttp.Config.Configuration do
field :disable_vpn_on_oidc_error, :boolean
# The defaults for these fields are set in the following migration:
# apps/fz_http/priv/repo/migrations/20221224210654_fix_sites_nullable_fields.exs
# 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
@@ -22,7 +22,7 @@ defmodule FzHttp.Config.Configuration do
field :default_client_mtu, :integer
field :default_client_endpoint, :string
field :default_client_dns, {:array, :string}, default: []
field :default_client_allowed_ips, {:array, FzHttp.Types.INET}, 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.
@@ -33,11 +33,11 @@ defmodule FzHttp.Config.Configuration do
embeds_one :logo, Logo, on_replace: :delete
embeds_many :openid_connect_providers,
FzHttp.Config.Configuration.OpenIDConnectProvider,
Domain.Config.Configuration.OpenIDConnectProvider,
on_replace: :delete
embeds_many :saml_identity_providers,
FzHttp.Config.Configuration.SAMLIdentityProvider,
Domain.Config.Configuration.SAMLIdentityProvider,
on_replace: :delete
timestamps()

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.Config.Configuration.Changeset do
use FzHttp, :changeset
import FzHttp.Config, only: [config_changeset: 2]
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
@@ -32,10 +32,10 @@ defmodule FzHttp.Config.Configuration.Changeset do
|> cast(attrs, @fields)
|> cast_embed(:logo)
|> cast_embed(:openid_connect_providers,
with: {FzHttp.Config.Configuration.OpenIDConnectProvider, :changeset, []}
with: {Domain.Config.Configuration.OpenIDConnectProvider, :changeset, []}
)
|> cast_embed(:saml_identity_providers,
with: {FzHttp.Config.Configuration.SAMLIdentityProvider, :changeset, []}
with: {Domain.Config.Configuration.SAMLIdentityProvider, :changeset, []}
)
|> trim_change(:default_client_dns)
|> trim_change(:default_client_endpoint)
@@ -48,7 +48,7 @@ defmodule FzHttp.Config.Configuration.Changeset do
defp ensure_no_overridden_changes(changeset) do
changed_keys = Map.keys(changeset.changes)
configs = FzHttp.Config.fetch_source_and_configs!(changed_keys)
configs = Domain.Config.fetch_source_and_configs!(changed_keys)
Enum.reduce(changed_keys, changeset, fn key, changeset ->
case Map.fetch!(configs, key) do

View File

@@ -1,10 +1,10 @@
defmodule FzHttp.Config.Configuration.OpenIDConnectProvider do
defmodule Domain.Config.Configuration.OpenIDConnectProvider do
@moduledoc """
OIDC Config virtual schema
"""
use FzHttp, :schema
use Domain, :schema
import Ecto.Changeset
alias FzHttp.Validator
alias Domain.Validator
@reserved_config_ids [
"identity",

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.Config.Configuration.SAMLIdentityProvider do
defmodule Domain.Config.Configuration.SAMLIdentityProvider do
@moduledoc """
SAML Config virtual schema
"""
use FzHttp, :schema
use Domain, :schema
import Ecto.Changeset
@reserved_config_ids [
@@ -48,7 +48,7 @@ defmodule FzHttp.Config.Configuration.SAMLIdentityProvider do
:metadata,
:auto_create_users
])
|> FzHttp.Validator.validate_uri(:base_url)
|> Domain.Validator.validate_uri(:base_url)
|> validate_metadata()
# Don't allow users to enter reserved config ids
|> validate_exclusion(:id, @reserved_config_ids)
@@ -69,7 +69,7 @@ defmodule FzHttp.Config.Configuration.SAMLIdentityProvider do
defp gen_default_base_url(changeset) do
default_base_url =
FzHttp.Config.fetch_env!(:fz_http, :external_url)
Domain.Config.fetch_env!(:web, :external_url)
|> Path.join("/auth/saml")
base_url = get_change(changeset, :base_url, default_base_url)

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Config.Definition do
defmodule Domain.Config.Definition do
@moduledoc """
This module provides a DSL to define application configuration, which can be read from multiple sources,
casted and validated.
@@ -6,7 +6,7 @@ defmodule FzHttp.Config.Definition do
## Examples
defmodule MyConfig do
use FzHttp.Config.Definition
use Domain.Config.Definition
@doc "My config key"
defconfig :my_key, :string, required: true
@@ -21,7 +21,7 @@ defmodule FzHttp.Config.Definition do
iex> MyConfig.fetch_doc(:my_key)
{:ok, "My config key"}
"""
alias FzHttp.Config.Errors
alias Domain.Config.Errors
@type array_opts :: [{:validate_unique, boolean()} | {:validate_length, Keyword.t()}]
@@ -53,17 +53,17 @@ defmodule FzHttp.Config.Definition do
defmacro __using__(_opts) do
quote do
import FzHttp.Config.Definition
import FzHttp.Config, only: [compile_config!: 1]
import Domain.Config.Definition
import Domain.Config, only: [compile_config!: 1]
# Accumulator keeps the list of defined config keys
Module.register_attribute(__MODULE__, :configs, accumulate: true)
# A `configs/0` function is injected before module is compiled
# exporting the aggregated list of config keys
@before_compile FzHttp.Config.Definition
@before_compile Domain.Config.Definition
@doc "See `FzHttp.Config.Definition.fetch_doc/2`"
@doc "See `Domain.Config.Definition.fetch_doc/2`"
def fetch_doc(key), do: fetch_doc(__MODULE__, key)
end
end
@@ -96,7 +96,7 @@ defmodule FzHttp.Config.Definition do
defmacro defconfig(key, type, opts \\ []) do
quote do
@configs {__MODULE__, unquote(key)}
@spec unquote(key)() :: {FzHttp.Config.Definition.type(), FzHttp.Config.Definition.opts()}
@spec unquote(key)() :: {Domain.Config.Definition.type(), Domain.Config.Definition.opts()}
def unquote(key)(), do: {unquote(type), unquote(opts)}
end
end

View File

@@ -1,4 +1,5 @@
defmodule FzHttp.Config.Definitions do
# 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,
but for zero-touch deployments we allow to override most of configuration options
@@ -27,10 +28,10 @@ defmodule FzHttp.Config.Definitions do
It means that if environment variable is set, it will be used, regardless of the database value,
and UI to edit database value will be disabled.
"""
use FzHttp.Config.Definition
alias FzHttp.Config.Dumper
alias FzHttp.Types
alias FzHttp.Config.{Configuration, Logo}
use Domain.Config.Definition
alias Domain.Config.Dumper
alias Domain.Types
alias Domain.Config.{Configuration, Logo}
def doc_sections do
[
@@ -156,8 +157,8 @@ defmodule FzHttp.Config.Definitions do
defconfig(:external_url, :string,
changeset: fn changeset, key ->
changeset
|> FzHttp.Validator.validate_uri(key, require_trailing_slash: true)
|> FzHttp.Validator.normalize_url(key)
|> Domain.Validator.validate_uri(key, require_trailing_slash: true)
|> Domain.Validator.normalize_url(key)
end
)
@@ -289,7 +290,7 @@ defmodule FzHttp.Config.Definitions do
)
defconfig(:database_parameters, :map,
default: %{application_name: "firezone-#{Application.spec(:fz_http, :vsn)}"},
default: %{application_name: "firezone-#{Application.spec(:domain, :vsn)}"},
dump: &Dumper.keyword/1
)
@@ -314,8 +315,8 @@ defmodule FzHttp.Config.Definitions do
legacy_keys: [{:env, "ADMIN_EMAIL", "0.9"}],
changeset: fn changeset, key ->
changeset
|> FzHttp.Validator.trim_change(key)
|> FzHttp.Validator.validate_email(key)
|> Domain.Validator.trim_change(key)
|> Domain.Validator.validate_email(key)
end
)
@@ -339,7 +340,7 @@ defmodule FzHttp.Config.Definitions do
"""
defconfig(:guardian_secret_key, :string,
sensitive: true,
changeset: &FzHttp.Validator.validate_base64/2
changeset: &Domain.Validator.validate_base64/2
)
@doc """
@@ -347,7 +348,7 @@ defmodule FzHttp.Config.Definitions do
"""
defconfig(:database_encryption_key, :string,
sensitive: true,
changeset: &FzHttp.Validator.validate_base64/2
changeset: &Domain.Validator.validate_base64/2
)
@doc """
@@ -355,7 +356,7 @@ defmodule FzHttp.Config.Definitions do
"""
defconfig(:secret_key_base, :string,
sensitive: true,
changeset: &FzHttp.Validator.validate_base64/2
changeset: &Domain.Validator.validate_base64/2
)
@doc """
@@ -363,7 +364,7 @@ defmodule FzHttp.Config.Definitions do
"""
defconfig(:live_view_signing_salt, :string,
sensitive: true,
changeset: &FzHttp.Validator.validate_base64/2
changeset: &Domain.Validator.validate_base64/2
)
@doc """
@@ -371,7 +372,7 @@ defmodule FzHttp.Config.Definitions do
"""
defconfig(:cookie_signing_salt, :string,
sensitive: true,
changeset: &FzHttp.Validator.validate_base64/2
changeset: &Domain.Validator.validate_base64/2
)
@doc """
@@ -379,7 +380,7 @@ defmodule FzHttp.Config.Definitions do
"""
defconfig(:cookie_encryption_salt, :string,
sensitive: true,
changeset: &FzHttp.Validator.validate_base64/2
changeset: &Domain.Validator.validate_base64/2
)
##############################################
@@ -455,8 +456,8 @@ defmodule FzHttp.Config.Definitions do
:string, changeset, key ->
changeset
|> FzHttp.Validator.trim_change(key)
|> FzHttp.Validator.validate_fqdn(key, allow_port: true)
|> Domain.Validator.trim_change(key)
|> Domain.Validator.validate_fqdn(key, allow_port: true)
end
)
@@ -477,8 +478,8 @@ defmodule FzHttp.Config.Definitions do
:string, changeset, key ->
changeset
|> FzHttp.Validator.trim_change(key)
|> FzHttp.Validator.validate_fqdn(key)
|> Domain.Validator.trim_change(key)
|> Domain.Validator.validate_fqdn(key)
end
)
@@ -537,7 +538,7 @@ defmodule FzHttp.Config.Definitions do
"""
defconfig(:saml_keyfile_path, :string,
default: "/var/firezone/saml.key",
changeset: &FzHttp.Validator.validate_file(&1, &2, extensions: ~w[.pem .key])
changeset: &Domain.Validator.validate_file(&1, &2, extensions: ~w[.pem .key])
)
@doc """
@@ -546,7 +547,7 @@ defmodule FzHttp.Config.Definitions do
"""
defconfig(:saml_certfile_path, :string,
default: "/var/firezone/saml.crt",
changeset: &FzHttp.Validator.validate_file(&1, &2, extensions: ~w[.crt .pem])
changeset: &Domain.Validator.validate_file(&1, &2, extensions: ~w[.crt .pem])
)
@doc """
@@ -673,12 +674,12 @@ defmodule FzHttp.Config.Definitions do
defconfig(:wireguard_ipv4_network, Types.CIDR,
default: "10.3.2.0/24",
changeset: &FzHttp.Validator.validate_ip_type_inclusion(&1, &2, [:ipv4])
changeset: &Domain.Validator.validate_ip_type_inclusion(&1, &2, [:ipv4])
)
defconfig(:wireguard_ipv4_address, Types.IP,
default: "10.3.2.1",
changeset: &FzHttp.Validator.validate_ip_type_inclusion(&1, &2, [:ipv4])
changeset: &Domain.Validator.validate_ip_type_inclusion(&1, &2, [:ipv4])
)
@doc """
@@ -689,19 +690,19 @@ defmodule FzHttp.Config.Definitions do
defconfig(:wireguard_ipv6_network, Types.CIDR,
default: "fd00::3:2:0/120",
changeset: &FzHttp.Validator.validate_ip_type_inclusion(&1, &2, [:ipv6])
changeset: &Domain.Validator.validate_ip_type_inclusion(&1, &2, [:ipv6])
)
defconfig(:wireguard_ipv6_address, Types.IP,
default: "fd00::3:2:1",
changeset: &FzHttp.Validator.validate_ip_type_inclusion(&1, &2, [:ipv6])
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: &FzHttp.Validator.validate_file(&1, &2)
# changeset: &Domain.Validator.validate_file(&1, &2)
)
defconfig(:wireguard_interface_name, :string, default: "wg-firezone")
@@ -737,8 +738,8 @@ defmodule FzHttp.Config.Definitions do
sensitive: true,
changeset: fn changeset, key ->
changeset
|> FzHttp.Validator.trim_change(key)
|> FzHttp.Validator.validate_email(key)
|> Domain.Validator.trim_change(key)
|> Domain.Validator.validate_email(key)
end
)
@@ -768,7 +769,7 @@ defmodule FzHttp.Config.Definitions do
Swoosh.Adapters.Sendmail,
Swoosh.Adapters.SocketLabs,
Swoosh.Adapters.SparkPost,
FzHttpWeb.Mailer.NoopAdapter,
Web.Mailer.NoopAdapter,
# DEPRECATED: Legacy options should be removed in 0.8
:smtp,
:mailgun,
@@ -778,7 +779,7 @@ defmodule FzHttp.Config.Definitions do
:sendmail
]
)},
default: FzHttpWeb.Mailer.NoopAdapter,
default: Web.Mailer.NoopAdapter,
legacy_keys: [{:env, "OUTBOUND_EMAIL_PROVIDER", "0.9"}],
dump: fn
:smtp -> Swoosh.Adapters.SMTP

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Config.Dumper do
defmodule Domain.Config.Dumper do
@doc ~S"""
Maps JSON-decoded ssl opts to pass to Erlang's ssl module. Most users
don't need to override many, if any, SSL opts. Most commonly this is

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.Config.Errors do
alias FzHttp.Config.Definition
defmodule Domain.Config.Errors do
alias Domain.Config.Definition
require Logger
@env_doc_url "https://www.firezone.dev/docs/reference/env-vars/#environment-variable-listing"
@@ -48,7 +48,7 @@ defmodule FzHttp.Config.Errors do
end
defp source({:app_env, key}), do: "application environment #{key}"
defp source({:env, key}), do: "environment variable #{FzHttp.Config.Resolver.env_key(key)}"
defp source({:env, key}), do: "environment variable #{Domain.Config.Resolver.env_key(key)}"
defp source({:db, key}), do: "database configuration #{key}"
defp source(:default), do: "default value"
@@ -93,7 +93,7 @@ defmodule FzHttp.Config.Errors do
def legacy_key_used(key, legacy_key, removed_at) do
Logger.warn(
"A legacy configuration option '#{legacy_key}' is used and it will be removed in v#{removed_at}. " <>
"Please use '#{FzHttp.Config.Resolver.env_key(key)}' configuration option instead."
"Please use '#{Domain.Config.Resolver.env_key(key)}' configuration option instead."
)
end
@@ -107,12 +107,12 @@ defmodule FzHttp.Config.Errors do
You can set this configuration via environment variable by adding it to `.env` file:
#{FzHttp.Config.Resolver.env_key(key)}=YOUR_VALUE
#{Domain.Config.Resolver.env_key(key)}=YOUR_VALUE
"""
end
defp db_example(key) do
if key in FzHttp.Config.Configuration.__schema__(:fields) do
if key in Domain.Config.Configuration.__schema__(:fields) do
"""
### Using database

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.Config.Fetcher do
alias FzHttp.Config.{Definition, Resolver, Caster, Validator}
defmodule Domain.Config.Fetcher do
alias Domain.Config.{Definition, Resolver, Caster, Validator}
@spec fetch_source_and_config(
module(),

View File

@@ -1,9 +1,9 @@
defmodule FzHttp.Config.Logo do
defmodule Domain.Config.Logo do
@moduledoc """
Embedded Schema for logo
"""
use FzHttp, :schema
import FzHttp.Validator
use Domain, :schema
import Domain.Validator
import Ecto.Changeset
@whitelisted_file_extensions ~w[.jpg .jpeg .png .gif .webp .avif .svg .tiff]
@@ -34,7 +34,7 @@ defmodule FzHttp.Config.Logo do
defp move_file_to_static(changeset) do
case fetch_change(changeset, :file) do
{:ok, file} ->
directory = Path.join(Application.app_dir(:fz_http), "priv/static/uploads/logo")
directory = Path.join(Application.app_dir(:domain), "priv/static/uploads/logo")
file_name = Path.basename(file)
file_path = Path.join(directory, file_name)
File.mkdir_p!(directory)

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.Config.Resolver do
alias FzHttp.Config.Errors
defmodule Domain.Config.Resolver do
alias Domain.Config.Errors
@type source :: {:env, atom()} | {:db, atom()} | :default
@@ -7,7 +7,7 @@ defmodule FzHttp.Config.Resolver do
key :: atom(),
env_configurations :: map(),
db_configurations :: map(),
opts :: [{:legacy_keys, [FzHttp.Config.Definition.legacy_key()]}]
opts :: [{:legacy_keys, [Domain.Config.Definition.legacy_key()]}]
) ::
{:ok, {source :: source(), value :: term()}} | :error
def resolve(key, env_configurations, db_configurations, opts) do

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Config.Validator do
defmodule Domain.Config.Validator do
import Ecto.Changeset
def validate(_key, nil, _type, _opts) do

View File

@@ -1,11 +1,11 @@
defmodule FzHttp.ConnectivityChecks do
defmodule Domain.ConnectivityChecks do
@moduledoc """
The ConnectivityChecks context.
"""
use Supervisor
alias FzHttp.Repo
alias FzHttp.Auth
alias FzHttp.ConnectivityChecks.{Poller, ConnectivityCheck, Authorizer}
alias Domain.Repo
alias Domain.Auth
alias Domain.ConnectivityChecks.{Poller, ConnectivityCheck, Authorizer}
@http_client_process_name __MODULE__.Finch
@@ -14,12 +14,12 @@ defmodule FzHttp.ConnectivityChecks do
end
def init(_opts) do
config = FzHttp.Config.fetch_env!(:fz_http, FzHttp.ConnectivityChecks)
config = Domain.Config.fetch_env!(:domain, Domain.ConnectivityChecks)
transport_opts = Keyword.fetch!(config, :http_client_options)
children =
if Keyword.fetch!(config, :enabled) == true do
application_version = Application.spec(:fz_http, :vsn) |> to_string()
application_version = Application.spec(:domain, :vsn) |> to_string()
connectivity_checks_url = Keyword.fetch!(config, :url)
request = Finch.build(:get, connectivity_checks_url <> application_version)

View File

@@ -1,10 +1,10 @@
defmodule FzHttp.ConnectivityChecks.Authorizer do
use FzHttp.Auth.Authorizer
alias FzHttp.ConnectivityChecks.ConnectivityCheck
defmodule Domain.ConnectivityChecks.Authorizer do
use Domain.Auth.Authorizer
alias Domain.ConnectivityChecks.ConnectivityCheck
def view_connectivity_checks_permission, do: build(ConnectivityCheck, :view)
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def list_permissions_for_role(:admin) do
[
view_connectivity_checks_permission()
@@ -15,7 +15,7 @@ defmodule FzHttp.ConnectivityChecks.Authorizer do
[]
end
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def for_subject(queryable, %Subject{} = subject) when is_user(subject) do
queryable
end

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.ConnectivityChecks.ConnectivityCheck do
use FzHttp, :schema
defmodule Domain.ConnectivityChecks.ConnectivityCheck do
use Domain, :schema
schema "connectivity_checks" do
field :response_body, :string

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.ConnectivityChecks.ConnectivityCheck.Changeset do
use FzHttp, :changeset
alias FzHttp.ConnectivityChecks.ConnectivityCheck
defmodule Domain.ConnectivityChecks.ConnectivityCheck.Changeset do
use Domain, :changeset
alias Domain.ConnectivityChecks.ConnectivityCheck
def create_changeset(attrs) do
%ConnectivityCheck{}

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.ConnectivityChecks.ConnectivityCheck.Query do
use FzHttp, :query
defmodule Domain.ConnectivityChecks.ConnectivityCheck.Query do
use Domain, :query
def all do
from(users in FzHttp.ConnectivityChecks.ConnectivityCheck, as: :connectivity_checks)
from(users in Domain.ConnectivityChecks.ConnectivityCheck, as: :connectivity_checks)
end
def by_id(queryable \\ all(), id) do

View File

@@ -1,10 +1,10 @@
defmodule FzHttp.ConnectivityChecks.Poller do
defmodule Domain.ConnectivityChecks.Poller do
@moduledoc """
A simple GenServer to periodically check for WAN connectivity by issuing
POSTs to https://ping[-dev].firez.one/{version}.
"""
use GenServer
alias FzHttp.ConnectivityChecks
alias Domain.ConnectivityChecks
require Logger
# Wait a minute before sending the first ping to avoid event spamming when
@@ -23,7 +23,7 @@ defmodule FzHttp.ConnectivityChecks.Poller do
@impl GenServer
def handle_info(:start_interval, %{request: request} = state) do
FzHttp.Config.fetch_env!(:fz_http, ConnectivityChecks)
Domain.Config.fetch_env!(:domain, ConnectivityChecks)
|> Keyword.fetch!(:interval)
|> :timer.seconds()
|> :timer.send_interval(:tick)

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Crypto do
defmodule Domain.Crypto do
@wg_psk_length 32
def psk do

View File

@@ -1,7 +1,7 @@
defmodule FzHttp.Devices do
alias FzHttp.{Repo, Config, Auth, Validator}
alias FzHttp.{Users, Telemetry}
alias FzHttp.Devices.{Device, Authorizer}
defmodule Domain.Devices do
alias Domain.{Repo, Config, Auth, Validator}
alias Domain.{Users, Telemetry}
alias Domain.Devices.{Device, Authorizer}
def count do
Device.Query.all()
@@ -156,7 +156,7 @@ defmodule FzHttp.Devices do
end
end
def generate_name(name \\ FzHttp.NameGenerator.generate()) do
def generate_name(name \\ Domain.NameGenerator.generate()) do
hash =
name
|> :erlang.phash2(2 ** 16)
@@ -200,14 +200,14 @@ defmodule FzHttp.Devices do
def inet(device) do
ips =
if Config.fetch_env!(:fz_http, :wireguard_ipv6_enabled) == true do
if Config.fetch_env!(:domain, :wireguard_ipv6_enabled) == true do
["#{device.ipv6}/128"]
else
[]
end
ips =
if Config.fetch_env!(:fz_http, :wireguard_ipv4_enabled) == true do
if Config.fetch_env!(:domain, :wireguard_ipv4_enabled) == true do
["#{device.ipv4}/32"] ++ ips
else
ips

View File

@@ -1,13 +1,13 @@
defmodule FzHttp.Devices.Authorizer do
use FzHttp.Auth.Authorizer
alias FzHttp.Devices.Device
defmodule Domain.Devices.Authorizer do
use Domain.Auth.Authorizer
alias Domain.Devices.Device
def view_own_devices_permission, do: build(Device, :view_own)
def manage_own_devices_permission, do: build(Device, :manage_own)
def manage_devices_permission, do: build(Device, :manage)
def configure_devices_permission, do: build(Device, :configure)
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def list_permissions_for_role(:admin) do
[
@@ -23,11 +23,11 @@ defmodule FzHttp.Devices.Authorizer do
view_own_devices_permission()
]
|> add_permission_if(
FzHttp.Config.fetch_config!(:allow_unprivileged_device_management),
Domain.Config.fetch_config!(:allow_unprivileged_device_management),
manage_own_devices_permission()
)
|> add_permission_if(
FzHttp.Config.fetch_config!(:allow_unprivileged_device_configuration),
Domain.Config.fetch_config!(:allow_unprivileged_device_configuration),
configure_devices_permission()
)
end
@@ -39,7 +39,7 @@ defmodule FzHttp.Devices.Authorizer do
defp add_permission_if(permissions, true, permission), do: permissions ++ [permission]
defp add_permission_if(permissions, false, _permission), do: permissions
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def for_subject(queryable, %Subject{} = subject) when is_user(subject) do
cond do
has_permission?(subject, manage_devices_permission()) ->

View File

@@ -1,12 +1,12 @@
defmodule FzHttp.Devices.Device do
use FzHttp, :schema
defmodule Domain.Devices.Device do
use Domain, :schema
schema "devices" do
field(:name, :string)
field(:description, :string)
field(:public_key, :string)
field(:preshared_key, FzHttp.Encrypted.Binary)
field(:preshared_key, Domain.Encrypted.Binary)
field(:use_default_allowed_ips, :boolean, read_after_writes: true, default: true)
field(:use_default_dns, :boolean, read_after_writes: true, default: true)
@@ -17,18 +17,18 @@ defmodule FzHttp.Devices.Device do
field(:endpoint, :string)
field(:mtu, :integer)
field(:persistent_keepalive, :integer)
field(:allowed_ips, {:array, FzHttp.Types.INET}, default: [])
field(:allowed_ips, {:array, Domain.Types.INET}, default: [])
field(:dns, {:array, :string}, default: [])
field(:ipv4, FzHttp.Types.IP)
field(:ipv6, FzHttp.Types.IP)
field(:ipv4, Domain.Types.IP)
field(:ipv6, Domain.Types.IP)
field(:remote_ip, FzHttp.Types.IP)
field(:remote_ip, Domain.Types.IP)
field(:rx_bytes, :integer)
field(:tx_bytes, :integer)
field(:latest_handshake, :utc_datetime_usec)
belongs_to(:user, FzHttp.Users.User)
belongs_to(:user, Domain.Users.User)
timestamps()
end

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.Devices.Device.Changeset do
use FzHttp, :changeset
import FzHttp.Config, only: [config_changeset: 3]
alias FzHttp.Users
alias FzHttp.Devices
defmodule Domain.Devices.Device.Changeset do
use Domain, :changeset
import Domain.Config, only: [config_changeset: 3]
alias Domain.Users
alias Domain.Devices
@create_fields ~w[name description
public_key preshared_key]a
@@ -34,8 +34,8 @@ defmodule FzHttp.Devices.Device.Changeset do
def create_changeset(attrs) do
%Devices.Device{}
|> cast(attrs, @create_fields)
|> put_default_value(:name, &FzHttp.Devices.generate_name/0)
|> put_default_value(:preshared_key, &FzHttp.Crypto.psk/0)
|> put_default_value(:name, &Domain.Devices.generate_name/0)
|> put_default_value(:preshared_key, &Domain.Crypto.psk/0)
|> changeset()
|> validate_base64(:public_key)
|> validate_base64(:preshared_key)
@@ -99,7 +99,7 @@ defmodule FzHttp.Devices.Device.Changeset do
end
defp maybe_put_default_ip(changeset, field) do
if FzHttp.Config.fetch_env!(:fz_http, :"wireguard_#{field}_enabled") == true do
if Domain.Config.fetch_env!(:domain, :"wireguard_#{field}_enabled") == true do
case fetch_field(changeset, field) do
{:data, nil} -> put_default_ip(changeset, field)
:error -> put_default_ip(changeset, field)
@@ -113,15 +113,15 @@ defmodule FzHttp.Devices.Device.Changeset do
defp put_default_ip(changeset, field) do
cidr = wireguard_network(field)
hosts = FzHttp.Types.CIDR.count_hosts(cidr)
hosts = Domain.Types.CIDR.count_hosts(cidr)
offset = Enum.random(2..(hosts - 2))
{:ok, gateway_address} =
FzHttp.Config.fetch_env!(:fz_http, :"wireguard_#{field}_address")
|> FzHttp.Types.IP.cast()
Domain.Config.fetch_env!(:domain, :"wireguard_#{field}_address")
|> Domain.Types.IP.cast()
Devices.Device.Query.next_available_address(cidr, offset, [gateway_address])
|> FzHttp.Repo.one()
|> Domain.Repo.one()
|> case do
nil -> add_error(changeset, :base, "CIDR #{cidr} is exhausted")
ip -> put_change(changeset, field, ip)
@@ -129,7 +129,7 @@ defmodule FzHttp.Devices.Device.Changeset do
end
defp wireguard_network(field) do
cidr = FzHttp.Config.fetch_env!(:fz_http, :"wireguard_#{field}_network")
cidr = Domain.Config.fetch_env!(:domain, :"wireguard_#{field}_network")
%{cidr | netmask: limit_cidr_netmask(field, cidr.netmask)}
end
@@ -137,13 +137,13 @@ defmodule FzHttp.Devices.Device.Changeset do
defp limit_cidr_netmask(:ipv6, network), do: max(network, 70)
defp ipv4_address do
FzHttp.Config.fetch_env!(:fz_http, :wireguard_ipv4_address)
|> FzHttp.Types.IP.cast()
Domain.Config.fetch_env!(:domain, :wireguard_ipv4_address)
|> Domain.Types.IP.cast()
end
defp ipv6_address do
FzHttp.Config.fetch_env!(:fz_http, :wireguard_ipv6_address)
|> FzHttp.Types.IP.cast()
Domain.Config.fetch_env!(:domain, :wireguard_ipv6_address)
|> Domain.Types.IP.cast()
end
defp validate_max_devices(changeset, user) do
@@ -151,7 +151,7 @@ defmodule FzHttp.Devices.Device.Changeset do
# At the moment it's not a big concern. Fixing it would require locking against INSERTs or DELETEs
# while counts are happening.
count = Devices.count_by_user_id(user.id)
max_devices = FzHttp.Config.fetch_env!(:fz_http, :max_devices_per_user)
max_devices = Domain.Config.fetch_env!(:domain, :max_devices_per_user)
if count >= max_devices do
add_error(

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.Devices.Device.Query do
use FzHttp, :query
defmodule Domain.Devices.Device.Query do
use Domain, :query
def all do
from(devices in FzHttp.Devices.Device, as: :devices)
from(devices in Domain.Devices.Device, as: :devices)
end
def by_id(queryable \\ all(), id) do
@@ -27,8 +27,8 @@ defmodule FzHttp.Devices.Device.Query do
def only_active(queryable \\ all()) do
dynamic =
if FzHttp.Config.vpn_sessions_expire?() do
vpn_session_duration = FzHttp.Config.fetch_config!(:vpn_session_duration)
if Domain.Config.vpn_sessions_expire?() do
vpn_session_duration = Domain.Config.fetch_config!(:vpn_session_duration)
dynamic(
[user: user],

View File

@@ -1,9 +1,9 @@
defmodule FzHttp.Devices.StatsUpdater do
defmodule Domain.Devices.StatsUpdater do
@moduledoc """
Extracts WireGuard data about each peer and adds it to
the correspond device.
"""
alias FzHttp.{Devices, Devices.Device, Repo}
alias Domain.{Devices, Devices.Device, Repo}
def update(stats) do
for {public_key, data} <- stats do

View File

@@ -0,0 +1,7 @@
defmodule Domain.Encrypted.Binary do
@moduledoc """
Configures how to encrpyt Binaries to the DB.
"""
use Cloak.Ecto.Binary, vault: Domain.Vault
end

View File

@@ -0,0 +1,7 @@
defmodule Domain.Encrypted.Map do
@moduledoc """
Configures how to encrpyt Maps to the DB.
"""
use Cloak.Ecto.Map, vault: Domain.Vault
end

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.NameGenerator do
defmodule Domain.NameGenerator do
@adjectives ~w(
abandoned able absolute adorable adventurous academic acceptable acclaimed accomplished
accurate aching acidic acrobatic active actual adept admirable admired adolescent adorable

View File

@@ -0,0 +1,97 @@
defmodule Domain.Notifications do
@moduledoc """
Notification notifications for notifications live view.
"""
use GenServer
alias Phoenix.PubSub
@topic "notifications_live"
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, [], opts)
end
@doc """
Gets a list of current notifications.
"""
def current, do: current(__MODULE__)
def current(nil), do: current()
def current(pid), do: GenServer.call(pid, :current)
@doc """
Add a notification.
"""
def add(notification), do: add(__MODULE__, notification)
def add(nil, notification), do: add(notification)
def add(pid, notification), do: GenServer.call(pid, {:add, notification})
@doc """
Clear all notifications.
"""
def clear_all, do: clear_all(__MODULE__)
def clear_all(nil), do: clear_all()
def clear_all(pid), do: GenServer.call(pid, :clear_all)
@doc """
Clear the given notification.
"""
def clear(notification), do: clear(__MODULE__, notification)
def clear(nil, notification), do: clear(notification)
def clear(pid, notification), do: GenServer.call(pid, {:clear, notification})
@doc """
Clear a notification at the given index.
"""
def clear_at(index), do: clear_at(__MODULE__, index)
def clear_at(nil, index), do: clear_at(index)
def clear_at(pid, index), do: GenServer.call(pid, {:clear_at, index})
defp broadcast(notifications) do
PubSub.broadcast(
Domain.PubSub,
@topic,
{:notifications, notifications}
)
end
@impl GenServer
def init(notifications) do
{:ok, notifications}
end
@impl GenServer
def handle_call(:current, _from, notifications) do
{:reply, notifications, notifications}
end
@impl GenServer
def handle_call({:add, notification}, _from, notifications) do
new_notifications = [notification | notifications]
broadcast(new_notifications)
{:reply, :ok, new_notifications}
end
@impl GenServer
def handle_call(:clear_all, _from, _notifications) do
broadcast([])
{:reply, :ok, []}
end
@impl GenServer
def handle_call({:clear, notification}, _from, notifications) do
new_notifications = Enum.reject(notifications, &(&1 == notification))
broadcast(new_notifications)
{:reply, :ok, new_notifications}
end
@impl GenServer
def handle_call({:clear_at, index}, _from, notifications) do
{_, new_notifications} = List.pop_at(notifications, index)
broadcast(new_notifications)
{:reply, :ok, new_notifications}
end
end

View File

@@ -1,17 +1,18 @@
defmodule FzHttp.Release do
alias FzHttp.{ApiTokens, Users}
defmodule Domain.Release do
alias Domain.{ApiTokens, Users}
require Logger
def migrate do
load_app()
@app :domain
@repos Application.compile_env!(:domain, :ecto_repos)
for repo <- FzHttp.Config.fetch_env!(:fz_http, :ecto_repos) do
def migrate do
for repo <- @repos do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def create_admin_user do
boot_database_app()
start_domain_app()
email = email()
@@ -48,7 +49,7 @@ defmodule FzHttp.Release do
end
def create_api_token(device \\ :stdio) do
boot_database_app()
start_domain_app()
device
|> IO.write(default_admin_user() |> mint_jwt())
@@ -69,16 +70,8 @@ defmodule FzHttp.Release do
Users.update_user(user, %{role: role})
end
def repos do
FzHttp.Config.fetch_env!(:fz_http, :ecto_repos)
end
defp email do
FzHttp.Config.fetch_env!(:fz_http, :admin_email)
end
defp set_supervision_tree_mode(mode) do
Application.put_env(:fz_http, :supervision_tree_mode, mode)
Domain.Config.fetch_env!(:domain, :admin_email)
end
defp default_admin_user do
@@ -90,29 +83,19 @@ defmodule FzHttp.Release do
defp mint_jwt(%Users.User{} = user) do
{:ok, api_token} = ApiTokens.create_api_token(user, %{})
{:ok, secret, _claims} = FzHttpWeb.Auth.JSON.Authentication.fz_encode_and_sign(api_token)
{:ok, secret, _claims} = Web.Auth.JSON.Authentication.fz_encode_and_sign(api_token)
secret
end
defp boot_database_app do
load_app()
set_supervision_tree_mode(:database)
start_app()
end
defp start_domain_app do
# Load the app
:ok = Application.ensure_loaded(@app)
defp load_app do
Application.load(:fz_http)
# Fixes ssl startup when connecting to SSL DBs.
# See https://elixirforum.com/t/ssl-connection-cannot-be-established-using-elixir-releases/25444/5
Application.ensure_all_started(:ssl)
end
defp start_app do
Application.ensure_all_started(:fz_http)
# Start the app dependencies
{:ok, _apps} = Application.ensure_all_started(@app)
end
defp default_password do
FzHttp.Config.fetch_env!(:fz_http, :default_admin_password)
Domain.Config.fetch_env!(:domain, :default_admin_password)
end
end

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.Repo do
defmodule Domain.Repo do
use Ecto.Repo,
otp_app: :fz_http,
otp_app: :domain,
adapter: Ecto.Adapters.Postgres
@doc """

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.Rules do
alias FzHttp.{Repo, Auth, Validator, Telemetry}
alias FzHttp.Rules.{Authorizer, Rule}
defmodule Domain.Rules do
alias Domain.{Repo, Auth, Validator, Telemetry}
alias Domain.Rules.{Authorizer, Rule}
def fetch_count_by_user_id(user_id, %Auth.Subject{} = subject) do
if Validator.valid_uuid?(user_id) do
@@ -99,19 +99,13 @@ defmodule FzHttp.Rules do
}
end
def port_rules_supported?, do: FzHttp.Config.fetch_env!(:fz_wall, :port_based_rules_supported)
def as_settings do
port_rules_supported?()
|> scope()
Rule.Query.by_empty_port_type()
|> Repo.all()
|> Enum.map(&setting_projection/1)
|> MapSet.new()
end
defp scope(true), do: Rule.Query.all()
defp scope(false), do: Rule.Query.by_empty_port_type()
def allowlist do
Rule.Query.by_action(:accept)
|> Repo.all()

View File

@@ -1,10 +1,10 @@
defmodule FzHttp.Rules.Authorizer do
use FzHttp.Auth.Authorizer
alias FzHttp.Rules.Rule
defmodule Domain.Rules.Authorizer do
use Domain.Auth.Authorizer
alias Domain.Rules.Rule
def manage_rules_permission, do: build(Rule, :manage)
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def list_permissions_for_role(:admin) do
[
manage_rules_permission()
@@ -15,7 +15,7 @@ defmodule FzHttp.Rules.Authorizer do
[]
end
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def for_subject(queryable, %Subject{} = subject) when is_user(subject) do
cond do
has_permission?(subject, manage_rules_permission()) ->

View File

@@ -0,0 +1,14 @@
defmodule Domain.Rules.Rule do
use Domain, :schema
schema "rules" do
field :action, Ecto.Enum, values: [:drop, :accept], default: :drop
field :destination, Domain.Types.INET
field :port_type, Ecto.Enum, values: [:tcp, :udp]
field :port_range, Domain.Types.Int4Range
belongs_to :user, Domain.Users.User
timestamps()
end
end

View File

@@ -1,13 +1,12 @@
defmodule FzHttp.Rules.Rule.Changeset do
use FzHttp, :changeset
alias FzHttp.Rules.Rule
defmodule Domain.Rules.Rule.Changeset do
use Domain, :changeset
alias Domain.Rules.Rule
@exclusion_msg "destination overlaps with an existing rule"
@port_range_msg "port is not within valid range"
@port_type_msg "port_type must be specified with port_range"
@fields ~w[action destination port_type port_range user_id]a
@port_based_fields ~w[port_type port_range]a
@required_fields ~w[action destination]a
def create_changeset(attrs) do
@@ -15,15 +14,8 @@ defmodule FzHttp.Rules.Rule.Changeset do
end
def update_changeset(rule, attrs) do
fields =
if FzHttp.Rules.port_rules_supported?() do
@fields
else
@fields -- @port_based_fields
end
rule
|> cast(attrs, fields)
|> cast(attrs, @fields)
|> validate_required(@required_fields)
|> validate_required_group(~w[port_range port_type]a)
|> check_constraint(:port_range,

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.Rules.Rule.Query do
use FzHttp, :query
defmodule Domain.Rules.Rule.Query do
use Domain, :query
def all do
from(rules in FzHttp.Rules.Rule, as: :rules)
from(rules in Domain.Rules.Rule, as: :rules)
end
def by_id(queryable \\ all(), id) do

View File

@@ -1,10 +1,10 @@
defmodule FzHttp.Telemetry do
defmodule Domain.Telemetry do
@moduledoc """
Functions for various telemetry events.
"""
use Supervisor
alias FzHttp.{Devices, Auth.MFA, Users}
alias FzHttp.Telemetry.{Timer, PostHog}
alias Domain.{Devices, Auth.MFA, Users}
alias Domain.Telemetry.{Timer, PostHog}
require Logger
def start_link(opts) do
@@ -12,7 +12,7 @@ defmodule FzHttp.Telemetry do
end
def init(_opts) do
config = FzHttp.Config.fetch_env!(:fz_http, FzHttp.Telemetry)
config = Domain.Config.fetch_env!(:domain, Domain.Telemetry)
if Keyword.fetch!(config, :enabled) == true do
children = [Timer]
@@ -79,8 +79,8 @@ defmodule FzHttp.Telemetry do
:ok
end
def fz_http_started do
PostHog.capture("fz_http_started", common_fields())
def domain_started do
PostHog.capture("domain_started", common_fields())
:ok
end
@@ -101,7 +101,7 @@ defmodule FzHttp.Telemetry do
disable_vpn_on_oidc_error: {_, disable_vpn_on_oidc_error},
logo: {_, logo}
} =
FzHttp.Config.fetch_source_and_configs!([
Domain.Config.fetch_source_and_configs!([
:openid_connect_providers,
:saml_identity_providers,
:allow_unprivileged_device_management,
@@ -127,10 +127,10 @@ defmodule FzHttp.Telemetry do
unprivileged_device_configuration: allow_unprivileged_device_configuration,
local_authentication: local_auth_enabled,
disable_vpn_on_oidc_error: disable_vpn_on_oidc_error,
outbound_email: FzHttpWeb.Mailer.active?(),
outbound_email: Web.Mailer.active?(),
external_database:
external_database?(Map.new(FzHttp.Config.fetch_env!(:fz_http, FzHttp.Repo))),
logo_type: FzHttp.Config.Logo.type(logo)
external_database?(Map.new(Domain.Config.fetch_env!(:domain, Domain.Repo))),
logo_type: Domain.Config.Logo.type(logo)
]
end
@@ -148,19 +148,19 @@ defmodule FzHttp.Telemetry do
end
def id do
FzHttp.Config.fetch_env!(:fz_http, __MODULE__)
Domain.Config.fetch_env!(:domain, __MODULE__)
|> Keyword.fetch!(:id)
end
defp fqdn do
:fz_http
|> FzHttp.Config.fetch_env!(FzHttpWeb.Endpoint)
:web
|> Domain.Config.fetch_env!(Web.Endpoint)
|> Keyword.get(:url)
|> Keyword.get(:host)
end
defp version do
Application.spec(:fz_http, :vsn) |> to_string()
Application.spec(:domain, :vsn) |> to_string()
end
defp external_database?(repo_conf) when is_map_key(repo_conf, :hostname) do

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Telemetry.PostHog do
defmodule Domain.Telemetry.PostHog do
require Logger
def capture(event, metadata) do

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.Telemetry.Timer do
defmodule Domain.Telemetry.Timer do
use GenServer
alias FzHttp.Telemetry
alias Domain.Telemetry
@initial_delay 60 * 1_000
@interval 43_200

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Types.CIDR do
defmodule Domain.Types.CIDR do
@moduledoc """
Ecto type implementation for CIDR's based on `Postgrex.INET` type, it required netmask to be always set.
"""
@@ -147,5 +147,5 @@ defmodule FzHttp.Types.CIDR do
def load(%Postgrex.INET{} = inet), do: {:ok, inet}
def load(_), do: :error
def to_string(%Postgrex.INET{} = inet), do: FzHttp.Types.INET.to_string(inet)
def to_string(%Postgrex.INET{} = inet), do: Domain.Types.INET.to_string(inet)
end

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Types.INET do
defmodule Domain.Types.INET do
@moduledoc """
INET is an implementation for native PostgreSQL `inet` type which can hold
either a CIDR (IP with a netmask) or just an IP address (with empty netmask).

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Types.Int4Range do
defmodule Domain.Types.Int4Range do
@moduledoc """
Ecto type for Postgres' Int4Range type.any()

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Types.IP do
defmodule Domain.Types.IP do
@moduledoc """
Ecto type implementation for IP's based on `Postgrex.INET` type,
it always ignores netmask by setting it to `nil`.
@@ -14,7 +14,7 @@ defmodule FzHttp.Types.IP do
def cast(%Postgrex.INET{} = inet), do: {:ok, inet}
def cast(binary) when is_binary(binary) do
with {:ok, address} <- FzHttp.Types.IPPort.cast_address(binary) do
with {:ok, address} <- Domain.Types.IPPort.cast_address(binary) do
{:ok, %Postgrex.INET{address: address, netmask: nil}}
else
{:error, _reason} -> {:error, message: "is invalid"}
@@ -30,5 +30,5 @@ defmodule FzHttp.Types.IP do
def load(_), do: :error
def to_string(ip) when is_binary(ip), do: ip
def to_string(%Postgrex.INET{} = inet), do: FzHttp.Types.INET.to_string(inet)
def to_string(%Postgrex.INET{} = inet), do: Domain.Types.INET.to_string(inet)
end

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Types.IPPort do
defmodule Domain.Types.IPPort do
@behaviour Ecto.Type
defstruct [:type, :address, :port]

View File

@@ -0,0 +1,21 @@
defimpl String.Chars, for: Postgrex.INET do
def to_string(%Postgrex.INET{} = inet), do: Domain.Types.INET.to_string(inet)
end
defimpl Phoenix.HTML.Safe, for: Postgrex.INET do
def to_iodata(%Postgrex.INET{} = inet), do: Domain.Types.INET.to_string(inet)
end
defimpl Jason.Encoder, for: Postgrex.INET do
def encode(%Postgrex.INET{} = struct, opts) do
Jason.Encode.string("#{struct}", opts)
end
end
defimpl String.Chars, for: Domain.Types.IPPort do
def to_string(%Domain.Types.IPPort{} = ip_port), do: Domain.Types.IPPort.to_string(ip_port)
end
defimpl Phoenix.HTML.Safe, for: Domain.Types.IPPort do
def to_iodata(%Domain.Types.IPPort{} = ip_port), do: Domain.Types.IPPort.to_string(ip_port)
end

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.Users do
alias FzHttp.{Repo, Auth, Validator, Config, Telemetry}
alias FzHttp.Users.{Authorizer, User}
defmodule Domain.Users do
alias Domain.{Repo, Auth, Validator, Config, Telemetry}
alias Domain.Users.{Authorizer, User}
require Ecto.Query
def count do
@@ -85,7 +85,7 @@ defmodule FzHttp.Users do
end
def consume_sign_in_token(%User{} = user, token) when is_binary(token) do
if FzHttp.Crypto.equal?(token, user.sign_in_token_hash) do
if Domain.Crypto.equal?(token, user.sign_in_token_hash) do
User.Query.by_id(user.id)
|> User.Query.where_sign_in_token_is_not_expired()
|> Ecto.Query.update(set: [sign_in_token_hash: nil, sign_in_token_created_at: nil])
@@ -151,8 +151,8 @@ defmodule FzHttp.Users do
|> Repo.update()
|> case do
{:ok, user} ->
FzHttp.Telemetry.disable_user()
FzHttpWeb.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})
Domain.Telemetry.disable_user()
Web.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})
{:ok, user}
{:error, reason} ->

View File

@@ -1,11 +1,11 @@
defmodule FzHttp.Users.Authorizer do
use FzHttp.Auth.Authorizer
alias FzHttp.Users.User
defmodule Domain.Users.Authorizer do
use Domain.Auth.Authorizer
alias Domain.Users.User
def manage_users_permission, do: build(User, :manage)
def edit_own_profile_permission, do: build(User, :edit_own_profile)
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def list_permissions_for_role(:admin) do
[
manage_users_permission(),
@@ -23,7 +23,7 @@ defmodule FzHttp.Users.Authorizer do
[]
end
@impl FzHttp.Auth.Authorizer
@impl Domain.Auth.Authorizer
def for_subject(queryable, %Subject{} = subject) when is_user(subject) do
cond do
has_permission?(subject, manage_users_permission()) ->

View File

@@ -1,5 +1,5 @@
defmodule FzHttp.Users.User do
use FzHttp, :schema
defmodule Domain.Users.User do
use Domain, :schema
schema "users" do
field :role, Ecto.Enum, values: [:unprivileged, :admin]
@@ -20,9 +20,9 @@ defmodule FzHttp.Users.User do
# Virtual fields that can be hydrated
field :device_count, :integer, virtual: true
has_many :devices, FzHttp.Devices.Device
has_many :oidc_connections, FzHttp.Auth.OIDC.Connection
has_many :api_tokens, FzHttp.ApiTokens.ApiToken
has_many :devices, Domain.Devices.Device
has_many :oidc_connections, Domain.Auth.OIDC.Connection
has_many :api_tokens, Domain.ApiTokens.ApiToken
field :disabled_at, :utc_datetime_usec
timestamps()

View File

@@ -1,7 +1,7 @@
defmodule FzHttp.Users.User.Changeset do
use FzHttp, :changeset
alias FzHttp.Auth
alias FzHttp.Users
defmodule Domain.Users.User.Changeset do
use Domain, :changeset
alias Domain.Auth
alias Domain.Users
@min_password_length 12
@max_password_length 64
@@ -75,7 +75,7 @@ defmodule FzHttp.Users.User.Changeset do
def generate_sign_in_token(%Users.User{} = user) do
user
|> change()
|> put_change(:sign_in_token, FzHttp.Crypto.rand_string())
|> put_change(:sign_in_token, Domain.Crypto.rand_string())
|> put_hash(:sign_in_token, to: :sign_in_token_hash)
|> put_change(:sign_in_token_created_at, DateTime.utc_now())
end

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.Users.User.Query do
use FzHttp, :query
defmodule Domain.Users.User.Query do
use Domain, :query
def all do
from(users in FzHttp.Users.User, as: :users)
from(users in Domain.Users.User, as: :users)
end
def by_id(queryable \\ all(), id)

View File

@@ -1,4 +1,4 @@
defmodule FzHttp.Validator do
defmodule Domain.Validator do
@doc """
A set of changeset helpers and schema extensions to simplify our changesets and make validation more reliable.
"""
@@ -156,7 +156,7 @@ defmodule FzHttp.Validator do
def validate_in_cidr(changeset, ip_field, cidr) do
validate_change(changeset, ip_field, fn _ip_field, ip ->
if FzHttp.Types.CIDR.contains?(cidr, ip) do
if Domain.Types.CIDR.contains?(cidr, ip) do
[]
else
[{ip_field, "is not in the CIDR #{cidr}"}]
@@ -166,7 +166,7 @@ defmodule FzHttp.Validator do
def validate_cidr(changeset, field, _opts \\ []) do
validate_change(changeset, field, fn _current_field, value ->
case FzHttp.Types.CIDR.cast(value) do
case Domain.Types.CIDR.cast(value) do
{:ok, _cidr} ->
[]
@@ -226,7 +226,7 @@ defmodule FzHttp.Validator do
def put_hash(%Ecto.Changeset{} = changeset, value_field, to: hash_field) do
with {:ok, value} when is_binary(value) and value != "" <-
fetch_change(changeset, value_field) do
put_change(changeset, hash_field, FzHttp.Crypto.hash(value))
put_change(changeset, hash_field, Domain.Crypto.hash(value))
else
_ -> changeset
end
@@ -238,7 +238,7 @@ defmodule FzHttp.Validator do
def validate_hash(changeset, value_field, hash_field: hash_field) do
with {:data, hash} <- fetch_field(changeset, hash_field) do
validate_change(changeset, value_field, fn value_field, token ->
if FzHttp.Crypto.equal?(token, hash) do
if Domain.Crypto.equal?(token, hash) do
[]
else
[{value_field, {"is invalid", [validation: :hash]}}]

View File

@@ -0,0 +1,6 @@
defmodule Domain.Vault do
@moduledoc """
Manages encrypted DB fields.
"""
use Cloak.Vault, otp_app: :domain
end

86
apps/domain/mix.exs Normal file
View File

@@ -0,0 +1,86 @@
defmodule Domain.MixProject do
use Mix.Project
def project do
[
app: :domain,
version: version(),
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.12",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
start_permanent: Mix.env() == :prod,
test_coverage: [tool: ExCoveralls],
preferred_cli_env: [
coveralls: :test,
"coveralls.detail": :test,
"coveralls.post": :test,
"coveralls.html": :test
],
aliases: aliases(),
deps: deps()
]
end
def version do
# Use dummy version for dev and test
System.get_env("VERSION", "0.0.0+git.0.deadbeef")
end
def application do
[
mod: {Domain.Application, []},
extra_applications: [
:logger,
:runtime_tools
]
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["test/support", "lib"]
defp elixirc_paths(_), do: ["lib"]
defp deps do
[
# Ecto-related deps
{:postgrex, "~> 0.16"},
{:decimal, "~> 2.0"},
{:ecto_sql, "~> 3.7"},
{:cloak, "~> 1.1"},
{:cloak_ecto, "~> 1.2"},
# PubSub
{:phoenix_pubsub, "~> 2.0"},
# Auth-related deps
{:plug_crypto, "~> 1.2"},
{:openid_connect, github: "firezone/openid_connect", branch: "master"},
{:argon2_elixir, "~> 2.0"},
{:nimble_totp, "~> 0.2"},
# Other deps
{:telemetry, "~> 1.0"},
{:posthog, "~> 0.1"},
# Runtime debugging
{:recon, "~> 2.5"},
{:observer_cli, "~> 1.7"},
# Test and dev deps
{:bypass, "~> 2.1", only: :test}
]
end
defp aliases do
[
"ecto.seed": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.setup": ["ecto.create", "ecto.migrate"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate", "test"]
]
end
end

View File

@@ -1,10 +1,10 @@
defmodule FzHttp.ApiTokensTest do
use FzHttp.DataCase, async: true
import FzHttp.ApiTokens
alias FzHttp.ApiTokens.{ApiToken, Authorizer}
alias FzHttp.ApiTokensFixtures
alias FzHttp.SubjectFixtures
alias FzHttp.UsersFixtures
defmodule Domain.ApiTokensTest do
use Domain.DataCase, async: true
import Domain.ApiTokens
alias Domain.ApiTokens.{ApiToken, Authorizer}
alias Domain.ApiTokensFixtures
alias Domain.SubjectFixtures
alias Domain.UsersFixtures
setup do
user = UsersFixtures.create_user_with_role(:admin)

View File

@@ -1,8 +1,8 @@
defmodule FzHttp.Auth.MFATest do
use FzHttp.DataCase, async: true
alias FzHttp.UsersFixtures
alias FzHttp.MFAFixtures
alias FzHttp.Auth.MFA
defmodule Domain.Auth.MFATest do
use Domain.DataCase, async: true
alias Domain.UsersFixtures
alias Domain.MFAFixtures
alias Domain.Auth.MFA
describe "count_users_with_mfa_enabled/0" do
test "returns 0 when there are no methods" do

View File

@@ -1,14 +1,14 @@
defmodule FzHttp.Auth.OIDC.RefresherTest do
use FzHttp.DataCase, async: true
alias FzHttp.Auth.OIDC.Refresher
alias FzHttp.UsersFixtures
defmodule Domain.Auth.OIDC.RefresherTest do
use Domain.DataCase, async: true
alias Domain.Auth.OIDC.Refresher
alias Domain.UsersFixtures
setup do
user = UsersFixtures.create_user_with_role(:admin)
{bypass, [provider_attrs]} = FzHttp.ConfigFixtures.start_openid_providers(["google"])
{bypass, [provider_attrs]} = Domain.ConfigFixtures.start_openid_providers(["google"])
conn =
Repo.insert!(%FzHttp.Auth.OIDC.Connection{
Repo.insert!(%Domain.Auth.OIDC.Connection{
user_id: user.id,
provider: "google",
refresh_token: "REFRESH_TOKEN"
@@ -19,7 +19,7 @@ defmodule FzHttp.Auth.OIDC.RefresherTest do
describe "refresh failed" do
test "disable user", %{user: user, conn: conn, bypass: bypass} do
FzHttp.ConfigFixtures.expect_refresh_token_failure(bypass)
Domain.ConfigFixtures.expect_refresh_token_failure(bypass)
assert Refresher.refresh(user.id) == {:stop, :shutdown, user.id}
user = Repo.reload(user)
@@ -32,7 +32,7 @@ defmodule FzHttp.Auth.OIDC.RefresherTest do
describe "refresh succeeded" do
test "does not change user", %{user: user, conn: conn, bypass: bypass} do
FzHttp.ConfigFixtures.expect_refresh_token(bypass)
Domain.ConfigFixtures.expect_refresh_token(bypass)
assert Refresher.refresh(user.id) == {:stop, :shutdown, user.id}
user = Repo.reload(user)

View File

@@ -1,7 +1,7 @@
defmodule FzHttp.AuthTest do
use FzHttp.DataCase
import FzHttp.Auth
alias FzHttp.ConfigFixtures
defmodule Domain.AuthTest do
use Domain.DataCase
import Domain.Auth
alias Domain.ConfigFixtures
describe "fetch_oidc_provider_config/1" do
test "returns error when provider does not exist" do
@@ -25,7 +25,7 @@ defmodule FzHttp.AuthTest do
end
test "puts default redirect_uri" do
FzHttp.Config.put_env_override(:external_url, "http://foo.bar.com/")
Domain.Config.put_env_override(:web, :external_url, "http://foo.bar.com/")
{_bypass, [attrs]} =
ConfigFixtures.start_openid_providers(["google"], %{"redirect_uri" => nil})

View File

@@ -1,6 +1,6 @@
defmodule FzHttp.Config.CasterTest do
defmodule Domain.Config.CasterTest do
use ExUnit.Case, async: true
import FzHttp.Config.Caster
import Domain.Config.Caster
describe "cast/2" do
test "casts a binary to an array of integers" do

View File

@@ -1,15 +1,15 @@
defmodule FzHttp.Config.DefinitionTest do
defmodule Domain.Config.DefinitionTest do
use ExUnit.Case, async: true
import FzHttp.Config.Definition
import Domain.Config.Definition
defmodule InvalidDefinitions do
use FzHttp.Config.Definition
use Domain.Config.Definition
defconfig(:required, Types.IP, foo: :bar)
end
defmodule Definitions do
use FzHttp.Config.Definition
use Domain.Config.Definition
defconfig(:required, Types.IP)
@@ -57,7 +57,7 @@ defmodule FzHttp.Config.DefinitionTest do
end
test "inserts a function which returns definition doc" do
assert fetch_doc(FzHttp.Config.Definitions, :default_admin_email) ==
assert fetch_doc(Domain.Config.Definitions, :default_admin_email) ==
{:ok, "Primary administrator email.\n"}
assert fetch_doc(Foo, :bar) ==

Some files were not shown because too many files have changed in this diff Show More