mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-28 10:18:51 +00:00
Merge pull request #341 from firezone/backlog/150/download_link
backlog/150: Download link
This commit is contained in:
@@ -27,4 +27,4 @@ page=$(curl -L -i -vvv -k https://localhost)
|
||||
echo $page
|
||||
|
||||
echo "Testing for sign in button"
|
||||
echo $page | grep "Sign in"
|
||||
echo $page | grep '<button class="button" type="submit">Sign In</button>'
|
||||
|
||||
@@ -3,6 +3,11 @@ defmodule FzCommon.FzString do
|
||||
Utility functions for working with Strings.
|
||||
"""
|
||||
|
||||
def sanitize_filename(str) when is_binary(str) do
|
||||
str
|
||||
|> String.replace(~r/[^a-zA-Z0-9]+/, "_")
|
||||
end
|
||||
|
||||
def to_boolean(str) when is_binary(str) do
|
||||
as_bool(String.downcase(str))
|
||||
end
|
||||
|
||||
@@ -3,6 +3,12 @@ defmodule FzCommon.FzStringTest do
|
||||
|
||||
alias FzCommon.FzString
|
||||
|
||||
describe "sanitize_filename/1" do
|
||||
test "santizes sequential spaces" do
|
||||
assert "Factory_Device" == FzString.sanitize_filename("Factory Device")
|
||||
end
|
||||
end
|
||||
|
||||
describe "to_boolean/1" do
|
||||
test "converts to true" do
|
||||
assert true == FzString.to_boolean("True")
|
||||
|
||||
@@ -12,3 +12,8 @@ nav.navbar {
|
||||
aside.aside {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: $interface-000;
|
||||
color: $interface-600;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
// its own CSS file.
|
||||
import css from "../css/app.scss"
|
||||
|
||||
/* Application fonts */
|
||||
import "@fontsource/fira-sans"
|
||||
import "@fontsource/open-sans"
|
||||
import "@fontsource/fira-mono"
|
||||
|
||||
// webpack automatically bundles all modules in your
|
||||
// entry points. Those entry points can be configured
|
||||
@@ -12,9 +15,33 @@ import "@fontsource/fira-sans"
|
||||
// Import dependencies
|
||||
//
|
||||
import "phoenix_html"
|
||||
import {Socket} from "phoenix"
|
||||
import {Socket, Presence} from "phoenix"
|
||||
import {LiveSocket} from "phoenix_live_view"
|
||||
import Hooks from "./hooks.js"
|
||||
import {FormatTimestamp} from "./util.js"
|
||||
|
||||
// User Socket
|
||||
const userToken = document
|
||||
.querySelector("meta[name='user-token']")
|
||||
.getAttribute("content")
|
||||
const userSocket = new Socket("/socket", {
|
||||
params: {
|
||||
token: userToken
|
||||
}
|
||||
})
|
||||
|
||||
// Notifications
|
||||
const channelToken = document
|
||||
.querySelector("meta[name='channel-token']")
|
||||
.getAttribute("content")
|
||||
const notificationChannel =
|
||||
userSocket.channel("notification:session", {
|
||||
token: channelToken,
|
||||
user_agent: window.navigator.userAgent
|
||||
})
|
||||
|
||||
// Presence
|
||||
const presence = new Presence(notificationChannel)
|
||||
|
||||
// LiveView setup
|
||||
const csrfToken = document
|
||||
@@ -31,8 +58,41 @@ const liveSocket = new LiveSocket(
|
||||
}
|
||||
)
|
||||
|
||||
/* XXX: Refactor this into a LiveView. */
|
||||
const sessionConnect = function (pres) {
|
||||
let tbody = document.getElementById("sessions-table-body")
|
||||
let rows = ""
|
||||
|
||||
pres.list((user_id, {metas: metas}) => {
|
||||
if (tbody) {
|
||||
metas.forEach(meta =>
|
||||
rows +=
|
||||
`<tr>
|
||||
<td>${FormatTimestamp(meta.online_at)}</td>
|
||||
<td>${FormatTimestamp(meta.last_signed_in_at)}</td>
|
||||
<td>${meta.remote_ip}</td>
|
||||
<td>${meta.user_agent}</td>
|
||||
</tr>`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
if (tbody && rows.length > 0) {
|
||||
tbody.innerHTML = rows
|
||||
}
|
||||
}
|
||||
|
||||
// uncomment to connect if there are any LiveViews on the page
|
||||
liveSocket.connect()
|
||||
userSocket.connect()
|
||||
|
||||
// function to receive session updates
|
||||
presence.onSync(() => sessionConnect(presence))
|
||||
|
||||
notificationChannel.join()
|
||||
// .receive("ok", ({messages}) => console.log("catching up", messages))
|
||||
// .receive("error", ({reason}) => console.log("error", reason))
|
||||
// .receive("timeout", () => console.log("Networking issue. Still waiting..."))
|
||||
|
||||
// expose liveSocket on window for web console debug logs and latency simulation:
|
||||
// >> liveSocket.enableDebug()
|
||||
@@ -40,7 +100,6 @@ liveSocket.connect()
|
||||
|
||||
window.liveSocket = liveSocket
|
||||
|
||||
|
||||
// Notification dismiss
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
(document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import moment from "moment"
|
||||
import hljs from "highlight.js"
|
||||
import {FormatTimestamp} from "./util.js"
|
||||
|
||||
const QRCode = require('qrcode')
|
||||
|
||||
@@ -19,12 +20,45 @@ const renderQrCode = function () {
|
||||
}
|
||||
}
|
||||
|
||||
/* XXX: Sad we have to write custom JS for this. Keep an eye on
|
||||
* https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html
|
||||
* in case a toggleClass function is implemented. The toggle()
|
||||
* function listed there automatically adds display: none which is not
|
||||
* what we want in this case.
|
||||
*/
|
||||
const toggleDropdown = function () {
|
||||
const button = this.el
|
||||
const dropdown = document.getElementById(button.dataset.target)
|
||||
|
||||
document.addEventListener("click", e => {
|
||||
let ancestor = e.target
|
||||
|
||||
do {
|
||||
if (ancestor == button) return
|
||||
ancestor = ancestor.parentNode
|
||||
} while(ancestor)
|
||||
|
||||
dropdown.classList.remove("is-active")
|
||||
})
|
||||
button.addEventListener("click", e => {
|
||||
dropdown.classList.toggle("is-active")
|
||||
})
|
||||
}
|
||||
|
||||
const highlightCode = function () {
|
||||
hljs.highlightAll()
|
||||
}
|
||||
|
||||
const formatTimestamp = function () {
|
||||
let timestamp = this.el.dataset.timestamp
|
||||
this.el.innerHTML = moment(timestamp).format("dddd, MMMM Do YYYY, h:mm:ss a z")
|
||||
let t = this.el.dataset.timestamp
|
||||
this.el.innerHTML = FormatTimestamp(t)
|
||||
}
|
||||
|
||||
let Hooks = {}
|
||||
Hooks.HighlightCode = {
|
||||
mounted: highlightCode,
|
||||
updated: highlightCode
|
||||
}
|
||||
Hooks.QrCode = {
|
||||
mounted: renderQrCode,
|
||||
updated: renderQrCode
|
||||
@@ -33,5 +67,9 @@ Hooks.FormatTimestamp = {
|
||||
mounted: formatTimestamp,
|
||||
updated: formatTimestamp
|
||||
}
|
||||
Hooks.ToggleDropdown = {
|
||||
mounted: toggleDropdown,
|
||||
updated: toggleDropdown
|
||||
}
|
||||
|
||||
export default Hooks
|
||||
|
||||
7
apps/fz_http/assets/js/util.js
Normal file
7
apps/fz_http/assets/js/util.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import moment from "moment"
|
||||
|
||||
const FormatTimestamp = function (timestamp) {
|
||||
return moment(timestamp).format("dddd, MMMM Do YYYY, h:mm:ss a z")
|
||||
}
|
||||
|
||||
export { FormatTimestamp }
|
||||
@@ -44,18 +44,27 @@ $default-padding: $size-base * 1.5;
|
||||
|
||||
/* Default font */
|
||||
$family-sans-serif: "Fira Sans", sans-serif;
|
||||
$family-primary: $family-sans-serif;
|
||||
|
||||
/* Monospace font */
|
||||
$family-monospace: "Fira Mono", monospace;
|
||||
|
||||
/* Text color */
|
||||
$text: $interface-200;
|
||||
$text-light: $interface-300;
|
||||
$text-strong: $interface-100;
|
||||
$text: $interface-100;
|
||||
$text-light: $interface-200;
|
||||
$text-strong: $interface-000;
|
||||
|
||||
/* Base color */
|
||||
$base-color: $interface-main;
|
||||
$base-color-light: rgba(24, 28, 33, .06);
|
||||
|
||||
/* General overrides */
|
||||
$primary: $accent-500;
|
||||
/* See https://coolors.co/331700-990c00-ff0000 */
|
||||
$info: $accent-300;
|
||||
$primary: $accent-main;
|
||||
$success: #80B900;
|
||||
$warning: #FFB900;
|
||||
$danger: #990C00;
|
||||
$body-background-color: $interface-800;
|
||||
$link: $accent-300;
|
||||
$link-visited: $accent-600;
|
||||
|
||||
2518
apps/fz_http/assets/package-lock.json
generated
2518
apps/fz_http/assets/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@
|
||||
"buffer": "^6.0.3",
|
||||
"buffer-from": "^1.1.1",
|
||||
"glob": "^7.1.7",
|
||||
"highlight.js": "^11.3.1",
|
||||
"moment": "^2.29.1",
|
||||
"phoenix": "file:../../../deps/phoenix",
|
||||
"phoenix_html": "file:../../../deps/phoenix_html",
|
||||
@@ -27,7 +28,9 @@
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/preset-env": "^7.16.0",
|
||||
"@creativebulma/bulma-tooltip": "^1.2.0",
|
||||
"@fontsource/fira-mono": "^4.5.0",
|
||||
"@fontsource/fira-sans": "^4.5.0",
|
||||
"@fontsource/open-sans": "^4.5.2",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"@mdi/font": "^6.5.95",
|
||||
"admin-one-bulma-dashboard": "file:local_modules/admin-one-bulma-dashboard",
|
||||
|
||||
13
apps/fz_http/assets/static/images/logo-text.svg
Normal file
13
apps/fz_http/assets/static/images/logo-text.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg width="332" height="187" viewBox="0 0 332 187" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_712_107)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M65.2377 53C85.1003 69.5361 64.5377 106.185 71.794 120.983C56.8827 99.5955 77.1987 81.2158 65.2377 53Z" fill="#EF7A30"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M76.2521 84.8288C89.2352 93.2609 73.6506 113.018 81.9473 118.075C90.3493 123.197 89.3985 101.911 101 107.371C88.9206 103.263 93.9315 128.607 78.9644 125.234C61.836 121.374 83.1374 94.1419 76.2521 84.8288Z" fill="#7F3900"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 113.701C25.7272 94.0085 59.4085 125.609 75.3027 127.584C95.743 130.123 90.2982 106.5 100.529 107.38C91.1018 108.516 96.7606 133.222 75.6838 133.985C52.7304 134.817 29.83 99.8369 1.00004 113.701H1Z" fill="#331700"/>
|
||||
<path d="M111.96 134H127.38V127.88H123.78V109.46H135.24V127.88H131.64V134H147V127.88H141.48V103.34H123.78V100.88C123.78 98.24 126.12 97.28 128.88 97.28C131.22 97.28 133.41 97.46 135.24 97.67C135.99 98.54 137.07 99.08 138.3 99.08C140.64 99.08 142.44 97.22 142.44 94.94C142.44 92.66 140.64 90.86 138.3 90.86C136.92 90.86 135.72 91.52 134.97 92.54C132.45 91.82 130.35 91.28 127.38 91.28C122.7 91.28 117.54 92.72 117.54 99.56V103.34H111.96V109.46H117.54V127.88H111.96V134ZM145.033 134H166.993V127.94H158.413V118.04C158.413 112.46 161.353 108.98 168.913 108.98V115.58H175.633V104.24C174.073 103.4 171.673 102.62 168.613 102.62C163.513 102.62 159.463 104.99 157.933 108.26V103.34H145.033V109.4H151.513V127.94H145.033V134ZM192.085 134.72C196.825 134.72 200.485 132.8 203.065 129.98L198.925 125.6C197.005 127.7 194.845 128.6 192.145 128.6C187.885 128.6 185.125 125.81 184.345 121.34H205.165C205.405 119.78 205.405 118.22 205.405 117.14C205.405 107.18 199.465 102.62 192.085 102.62C183.145 102.62 177.445 108.68 177.445 118.64C177.445 128.36 182.965 134.72 192.085 134.72ZM192.085 108.68C195.265 108.68 198.205 110.72 198.625 115.46H184.345C185.065 111.2 187.465 108.68 192.085 108.68ZM209.798 134H234.938V127.82H218.618L234.998 108.44V103.34H210.938V109.52H226.178L209.798 128.78V134ZM253.371 134.72C262.491 134.72 268.071 128.54 268.071 118.7C268.071 108.86 262.491 102.62 253.371 102.62C244.311 102.62 238.671 108.92 238.671 118.7C238.671 128.6 244.311 134.72 253.371 134.72ZM253.371 128.12C248.391 128.12 245.631 124.4 245.631 118.7C245.631 112.94 248.391 109.16 253.371 109.16C258.471 109.16 261.111 113 261.111 118.7C261.111 124.4 258.411 128.12 253.371 128.12ZM270.903 134H277.803V116.6C277.803 110.9 280.923 109.16 284.583 109.16C288.063 109.16 290.943 111.08 290.943 116.72V134H297.843V115.28C297.843 105.86 292.623 102.62 286.503 102.62C282.633 102.62 279.333 104.33 277.803 106.28V103.34H270.903V134ZM315.976 134.72C320.716 134.72 324.376 132.8 326.956 129.98L322.816 125.6C320.896 127.7 318.736 128.6 316.036 128.6C311.776 128.6 309.016 125.81 308.236 121.34H329.056C329.296 119.78 329.296 118.22 329.296 117.14C329.296 107.18 323.356 102.62 315.976 102.62C307.036 102.62 301.336 108.68 301.336 118.64C301.336 128.36 306.856 134.72 315.976 134.72ZM315.976 108.68C319.156 108.68 322.096 110.72 322.516 115.46H308.236C308.956 111.2 311.356 108.68 315.976 108.68Z" fill="#281303"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_712_107">
|
||||
<rect width="332" height="187" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -28,6 +28,7 @@ defmodule FzHttp.Application do
|
||||
FzHttp.Vault,
|
||||
FzHttpWeb.Endpoint,
|
||||
{Phoenix.PubSub, name: FzHttp.PubSub},
|
||||
FzHttpWeb.Presence,
|
||||
FzHttp.ConnectivityCheckService
|
||||
]
|
||||
end
|
||||
@@ -38,7 +39,8 @@ defmodule FzHttp.Application do
|
||||
FzHttp.Repo,
|
||||
FzHttp.Vault,
|
||||
FzHttpWeb.Endpoint,
|
||||
{Phoenix.PubSub, name: FzHttp.PubSub}
|
||||
{Phoenix.PubSub, name: FzHttp.PubSub},
|
||||
FzHttpWeb.Presence
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,6 +20,10 @@ defmodule FzHttp.Devices do
|
||||
Repo.all(from d in Device, where: d.user_id == ^user_id)
|
||||
end
|
||||
|
||||
def count(user_id) do
|
||||
Repo.one(from d in Device, where: d.user_id == ^user_id, select: count())
|
||||
end
|
||||
|
||||
def get_device!(id), do: Repo.get!(Device, id)
|
||||
|
||||
def create_device(attrs \\ %{}) do
|
||||
@@ -92,4 +96,41 @@ defmodule FzHttp.Devices do
|
||||
|> Enum.map(fn field -> {field, Device.field(changeset, field)} end)
|
||||
|> Map.new()
|
||||
end
|
||||
|
||||
def as_config(device) do
|
||||
wireguard_port = Application.fetch_env!(:fz_vpn, :wireguard_port)
|
||||
|
||||
"""
|
||||
[Interface]
|
||||
PrivateKey = #{device.private_key}
|
||||
Address = #{ipv4_address(device)}/32, #{ipv6_address(device)}/128
|
||||
#{dns_servers_config(device)}
|
||||
|
||||
[Peer]
|
||||
PublicKey = #{device.server_public_key}
|
||||
AllowedIPs = #{allowed_ips(device)}
|
||||
Endpoint = #{endpoint(device)}:#{wireguard_port}
|
||||
"""
|
||||
end
|
||||
|
||||
defp dns_servers_config(device) when is_struct(device) do
|
||||
dns_servers = dns_servers(device)
|
||||
|
||||
if dns_servers_empty?(dns_servers) do
|
||||
""
|
||||
else
|
||||
"DNS = #{dns_servers}"
|
||||
end
|
||||
end
|
||||
|
||||
defp dns_servers_empty?(nil), do: true
|
||||
|
||||
defp dns_servers_empty?(dns_servers) when is_binary(dns_servers) do
|
||||
len =
|
||||
dns_servers
|
||||
|> String.trim()
|
||||
|> String.length()
|
||||
|
||||
len == 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -56,6 +56,7 @@ defmodule FzHttpWeb do
|
||||
quote do
|
||||
use Phoenix.LiveView, layout: {FzHttpWeb.LayoutView, "live.html"}
|
||||
import FzHttpWeb.LiveHelpers
|
||||
alias Phoenix.LiveView.JS
|
||||
|
||||
@events_module Application.compile_env!(:fz_http, :events_module)
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
defmodule FzHttpWeb.NotificationChannel do
|
||||
@moduledoc """
|
||||
Handles dispatching realtime notifications to users' browser sessions.
|
||||
"""
|
||||
use FzHttpWeb, :channel
|
||||
alias FzHttp.Users
|
||||
alias FzHttpWeb.Presence
|
||||
|
||||
@impl Phoenix.Channel
|
||||
def join("notification:session", %{"user_agent" => user_agent, "token" => token}, socket) do
|
||||
case Phoenix.Token.verify(socket, "channel auth", token, max_age: 86_400) do
|
||||
{:ok, user_id} ->
|
||||
socket =
|
||||
socket
|
||||
|> assign(:current_user, Users.get_user!(user_id))
|
||||
|> assign(:user_agent, user_agent)
|
||||
|
||||
send(self(), :after_join)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:current_user, Users.get_user!(user_id))}
|
||||
|
||||
{:error, _} ->
|
||||
{:error, %{reason: "unauthorized"}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Phoenix.Channel
|
||||
def handle_info(:after_join, socket) do
|
||||
track(socket)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp track(socket) do
|
||||
user = socket.assigns.current_user
|
||||
|
||||
tracking_info = %{
|
||||
email: user.email,
|
||||
online_at: DateTime.utc_now(),
|
||||
last_signed_in_at: user.last_signed_in_at,
|
||||
remote_ip: socket.assigns.remote_ip,
|
||||
user_agent: socket.assigns.user_agent
|
||||
}
|
||||
|
||||
{:ok, _} = Presence.track(socket, user.id, tracking_info)
|
||||
|
||||
push(socket, "presence_state", presence_list(socket))
|
||||
end
|
||||
|
||||
defp presence_list(socket) do
|
||||
ids_to_show = [Integer.to_string(socket.assigns.current_user.id)]
|
||||
|
||||
Presence.list(socket)
|
||||
|> Map.take(ids_to_show)
|
||||
end
|
||||
end
|
||||
11
apps/fz_http/lib/fz_http_web/channels/presence.ex
Normal file
11
apps/fz_http/lib/fz_http_web/channels/presence.ex
Normal file
@@ -0,0 +1,11 @@
|
||||
defmodule FzHttpWeb.Presence do
|
||||
@moduledoc """
|
||||
Provides presence tracking to channels and processes.
|
||||
|
||||
See the [`Phoenix.Presence`](https://hexdocs.pm/phoenix/Phoenix.Presence.html)
|
||||
docs for more details.
|
||||
"""
|
||||
use Phoenix.Presence,
|
||||
otp_app: :fz_http,
|
||||
pubsub_server: FzHttp.PubSub
|
||||
end
|
||||
@@ -1,8 +1,11 @@
|
||||
defmodule FzHttpWeb.UserSocket do
|
||||
use Phoenix.Socket
|
||||
|
||||
alias FzHttp.Users
|
||||
|
||||
## Channels
|
||||
# channel "room:*", FzHttpWeb.RoomChannel
|
||||
channel "notification:session", FzHttpWeb.NotificationChannel
|
||||
|
||||
# Socket params are passed from the client and can
|
||||
# be used to verify and authenticate a user. After
|
||||
@@ -15,8 +18,19 @@ defmodule FzHttpWeb.UserSocket do
|
||||
#
|
||||
# See `Phoenix.Token` documentation for examples in
|
||||
# performing token verification on connect.
|
||||
def connect(_params, socket, _connect_info) do
|
||||
{:ok, socket}
|
||||
def connect(%{"token" => token}, socket, connect_info) do
|
||||
ip = get_ip_address(connect_info)
|
||||
|
||||
case Phoenix.Token.verify(socket, "user auth", token, max_age: 86_400) do
|
||||
{:ok, user_id} ->
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:current_user, Users.get_user!(user_id))
|
||||
|> assign(:remote_ip, ip)}
|
||||
|
||||
{:error, _} ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
# Socket id's are topics that allow you to identify all sockets for a given user:
|
||||
@@ -31,4 +45,35 @@ defmodule FzHttpWeb.UserSocket do
|
||||
# Returning `nil` makes this socket anonymous.
|
||||
# def id(_socket), do: nil
|
||||
def id(socket), do: "user_socket:#{socket.assigns.current_user.id}"
|
||||
|
||||
defp get_ip_address(%{peer_data: %{address: address}}) do
|
||||
convert_ip(address)
|
||||
|
||||
address
|
||||
|> Tuple.to_list()
|
||||
|> Enum.join(".")
|
||||
end
|
||||
|
||||
defp get_ip_address(%{x_headers: headers_list}) do
|
||||
header = Enum.find(headers_list, fn {key, _val} -> key == "x-real-ip" end)
|
||||
|
||||
case header do
|
||||
{_key, value} -> value
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
# IPv4
|
||||
defp convert_ip({_, _, _, _} = address) do
|
||||
address
|
||||
|> Tuple.to_list()
|
||||
|> Enum.join(".")
|
||||
end
|
||||
|
||||
# IPv6
|
||||
defp convert_ip(address) do
|
||||
address
|
||||
|> Tuple.to_list()
|
||||
|> Enum.join(":")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,10 +4,26 @@ defmodule FzHttpWeb.DeviceController do
|
||||
"""
|
||||
use FzHttpWeb, :controller
|
||||
|
||||
import FzCommon.FzString, only: [sanitize_filename: 1]
|
||||
alias FzHttp.Devices
|
||||
|
||||
plug :redirect_unauthenticated
|
||||
|
||||
def index(conn, _params) do
|
||||
conn
|
||||
|> redirect(to: Routes.device_index_path(conn, :index))
|
||||
end
|
||||
|
||||
def download_config(conn, %{"id" => device_id}) do
|
||||
device = Devices.get_device!(device_id)
|
||||
filename = "#{sanitize_filename(device.name)}.conf"
|
||||
content_type = "text/plain"
|
||||
|
||||
conn
|
||||
|> send_download(
|
||||
{:binary, Devices.as_config(device)},
|
||||
filename: filename,
|
||||
content_type: content_type
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule FzHttpWeb.Endpoint do
|
||||
end
|
||||
|
||||
socket "/socket", FzHttpWeb.UserSocket,
|
||||
websocket: true,
|
||||
websocket: [connect_info: [:peer_data, :x_headers]],
|
||||
longpoll: false
|
||||
|
||||
socket "/live", Phoenix.LiveView.Socket,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div>
|
||||
<.form let={f} for={@changeset} id="account-edit" phx-target={@myself} phx-submit="save">
|
||||
<div class="content">
|
||||
<div class="block">
|
||||
<p>Change email or enter new password below.</p>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="content">
|
||||
<div class="block">
|
||||
<p>Enter your current password to make these changes.</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<%= if @live_action == :edit do %>
|
||||
<%= live_modal(
|
||||
FzHttpWeb.AccountLive.FormComponent,
|
||||
return_to: Routes.account_show_path(@socket, :show),
|
||||
title: "Edit Account",
|
||||
id: "user-#{@current_user.id}",
|
||||
user: @current_user,
|
||||
action: @live_action) %>
|
||||
FzHttpWeb.AccountLive.FormComponent,
|
||||
return_to: Routes.account_show_path(@socket, :show),
|
||||
title: "Edit Account",
|
||||
id: "user-#{@current_user.id}",
|
||||
user: @current_user,
|
||||
action: @live_action) %>
|
||||
<% end %>
|
||||
|
||||
<%= render FzHttpWeb.SharedView, "heading.html", page_title: @page_title %>
|
||||
@@ -13,44 +13,63 @@
|
||||
<section class="section is-main-section">
|
||||
<%= render FzHttpWeb.SharedView, "flash.html", assigns %>
|
||||
|
||||
<div class="tile is-ancestor">
|
||||
<div class="tile is-parent">
|
||||
<div class="card tile is-child">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">
|
||||
Details
|
||||
</p>
|
||||
</header>
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h4 class="title is-4">Details</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<%= render FzHttpWeb.SharedView, "user_details.html", user: @current_user %>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<div class="field is-grouped card-footer-item">
|
||||
<p class="control">
|
||||
<%= live_patch(to: Routes.account_show_path(@socket, :edit), class: "button is-primary") do %>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-edit"></i>
|
||||
</span>
|
||||
<span>Change Email or Password</span>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="control">
|
||||
<%# This is purposefully a synchronous form in order to easily clear the session %>
|
||||
<%= form_for @changeset, Routes.user_path(@socket, :delete), [id: "delete-account", method: :delete], fn _f -> %>
|
||||
<%= submit(class: "button is-danger", data: [confirm: "Are you sure?"]) do %>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>Delete Your Account</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<%= live_patch(to: Routes.account_show_path(@socket, :edit), class: "button") do %>
|
||||
<span class="icon is-small">
|
||||
<i class="mdi mdi-pencil"></i>
|
||||
</span>
|
||||
<span>Change Email or Password</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render FzHttpWeb.SharedView, "user_details.html", user: @current_user %>
|
||||
</section>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<h4 class="title is-4">
|
||||
Active Sessions
|
||||
</h4>
|
||||
|
||||
<div class="block">
|
||||
<p>
|
||||
Your active Firezone web sessions. Each row corresponds to an open browser
|
||||
tab connected to Firezone.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<table class="table is-bordered is-hoverable is-striped is-fullwidth">
|
||||
<thead>
|
||||
<th>Came Online</th>
|
||||
<th>Last Signed In</th>
|
||||
<th>Remote IP</th>
|
||||
<th>User Agent</th>
|
||||
</thead>
|
||||
<tbody id="sessions-table-body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="section is-main-section">
|
||||
<h4 class="title is-4">
|
||||
Danger Zone
|
||||
</h4>
|
||||
|
||||
<%# This is purposefully a synchronous form in order to easily clear the session %>
|
||||
<%= form_for @changeset, Routes.user_path(@socket, :delete), [id: "delete-account", method: :delete], fn _f -> %>
|
||||
<%= submit(class: "button is-danger", data: [confirm: "Are you sure?"]) do %>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>Delete Your Account</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
<section class="section is-main-section">
|
||||
<%= render FzHttpWeb.SharedView, "flash.html", assigns %>
|
||||
|
||||
<div class="content">
|
||||
<div class="block">
|
||||
<p>
|
||||
Firezone periodically checks for WAN connectivity to the Internet and logs
|
||||
the result here. This is used to determine the public IP address of this
|
||||
server for populating the default endpoint field in device configurations.
|
||||
</p>
|
||||
</div>
|
||||
<div class="block">
|
||||
<table class="table is-bordered is-hoverable is-striped is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -11,7 +11,7 @@ defmodule FzHttpWeb.ConnectivityCheckLive.Index do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign_defaults(params, session, &load_data/2)
|
||||
|> assign(:page_title, "Connectivity Checks")}
|
||||
|> assign(:page_title, "WAN Connectivity Checks")}
|
||||
end
|
||||
|
||||
defp load_data(_params, socket) do
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<section class="section is-main-section">
|
||||
<%= render FzHttpWeb.SharedView, "flash.html", assigns %>
|
||||
|
||||
<div class="content">
|
||||
<div class="block">
|
||||
<%= render FzHttpWeb.SharedView, "devices_table.html", devices: @devices, socket: @socket %>
|
||||
</div>
|
||||
|
||||
<button class="button is-primary" phx-click="create_device">
|
||||
<button class="button" phx-click="create_device">
|
||||
Add Device
|
||||
</button>
|
||||
</section>
|
||||
|
||||
@@ -11,7 +11,7 @@ defmodule FzHttpWeb.DeviceLive.Index do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign_defaults(params, session, &load_data/2)
|
||||
|> assign(:page_title, "All Devices")}
|
||||
|> assign(:page_title, "Devices")}
|
||||
end
|
||||
|
||||
def handle_event("create_device", _params, socket) do
|
||||
|
||||
@@ -8,104 +8,125 @@
|
||||
action: @live_action) %>
|
||||
<% end %>
|
||||
|
||||
<%= render FzHttpWeb.SharedView, "heading.html", page_title: @page_title %>
|
||||
<%= render FzHttpWeb.SharedView, "heading.html", page_title: "Devices -> #{@page_title}" %>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<%= render FzHttpWeb.SharedView, "flash.html", assigns %>
|
||||
<div class="tile is-ancestor is-flex-wrap-wrap">
|
||||
<div class="tile is-parent">
|
||||
<div class="card tile is-child">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">Details</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<dl>
|
||||
<dt><strong>User</strong></dt>
|
||||
<dd><%= link(@user.email, to: Routes.user_show_path(@socket, :show, @user)) %></dd>
|
||||
|
||||
<dt><strong>Name</strong></dt>
|
||||
<dd><%= @device.name %></dd>
|
||||
|
||||
<dt><strong>Interface IP</strong></dt>
|
||||
<dd>
|
||||
<%= FzHttp.Devices.ipv4_address(@device) %>,
|
||||
<%= FzHttp.Devices.ipv6_address(@device) %>
|
||||
</dd>
|
||||
|
||||
<dt><strong>Allowed IPs</strong></dt>
|
||||
<dd><%= @allowed_ips %></dd>
|
||||
|
||||
<dt><strong>DNS Servers</strong></dt>
|
||||
<dd><%= @dns_servers || "None" %></dd>
|
||||
|
||||
<dt><strong>Endpoint</strong></dt>
|
||||
<dd><%= @endpoint %></dd>
|
||||
|
||||
<dt><strong>Public key</strong></dt>
|
||||
<dd class="code"><%= @device.public_key %></dd>
|
||||
|
||||
<dt><strong>Private key</strong></dt>
|
||||
<dd class="code"><%= @device.private_key %></dd>
|
||||
|
||||
<dt><strong>Server public key</strong></dt>
|
||||
<dd class="code"><%= @device.server_public_key %></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<div class="field is-grouped card-footer-item">
|
||||
<p class="control">
|
||||
<%= live_patch(to: Routes.device_show_path(@socket, :edit, @device), class: "button is-primary") do %>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-edit"></i>
|
||||
</span>
|
||||
<span>Edit</span>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="control">
|
||||
<button class="button is-danger"
|
||||
phx-click="delete_device"
|
||||
phx-value-device_id={@device.id}
|
||||
data-confirm="Are you sure? This will remove all data associated with this device.">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h4 class="title is-4">Details</h4>
|
||||
</div>
|
||||
<div class="tile is-parent">
|
||||
<div class="card tile is-child">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">Config</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<h6 class="is-6 title">
|
||||
Add the following to your WireGuard configuration file:
|
||||
</h6>
|
||||
<pre><code id="wg-conf">
|
||||
[Interface]
|
||||
PrivateKey = <%= @device.private_key %>
|
||||
Address = <%= FzHttp.Devices.ipv4_address(@device) %>/32, <%= FzHttp.Devices.ipv6_address(@device) %>/128
|
||||
<%= @dns_servers %>
|
||||
<div class="level-right">
|
||||
<%= live_patch(to: Routes.device_show_path(@socket, :edit, @device), class: "button") do %>
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-pencil"></i>
|
||||
</span>
|
||||
<span>Edit</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
[Peer]
|
||||
PublicKey = <%= @device.server_public_key %>
|
||||
AllowedIPs = <%= @allowed_ips %>
|
||||
Endpoint = <%= @endpoint %>:<%= @wireguard_port %></code></pre>
|
||||
<hr>
|
||||
<h6 class="is-6 title">
|
||||
Or scan the QR code with your mobile phone:
|
||||
</h6>
|
||||
<div class="has-text-centered">
|
||||
<canvas id="qr-canvas" phx-hook="QrCode"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table is-bordered is-hoverable is-striped is-fullwidth">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>User</strong></td>
|
||||
<td><%= link(@user.email, to: Routes.user_show_path(@socket, :show, @user)) %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Name</strong></td>
|
||||
<td><%= @device.name %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Interface IP</strong></td>
|
||||
<td>
|
||||
<%= FzHttp.Devices.ipv4_address(@device) %>,
|
||||
<%= FzHttp.Devices.ipv6_address(@device) %>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Allowed IPs</strong></td>
|
||||
<td><%= @allowed_ips %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>DNS Servers</strong></td>
|
||||
<td><%= @dns_servers || "None" %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Endpoint</strong></td>
|
||||
<td><%= @endpoint %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Public key</strong></td>
|
||||
<td class="code"><%= @device.public_key %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Private key</strong></td>
|
||||
<td class="code"><%= @device.private_key %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Server public key</strong></td>
|
||||
<td class="code"><%= @device.server_public_key %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h4 class="title is-4">WireGuard Configuration</h4>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<%= link(
|
||||
to: Routes.device_path(@socket, :download_config, @device),
|
||||
class: "button") do %>
|
||||
<span class="icon is-small">
|
||||
<i class="mdi mdi-download"></i>
|
||||
</span>
|
||||
<span>Download Configuration</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
Install the
|
||||
<a href="https://www.wireguard.com/install/">
|
||||
official WireGuard client
|
||||
</a>
|
||||
for your device, then use the below WireGuard configuration to connect.
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<pre><code id="wg-conf" class="language-toml"><%= @config %></code></pre>
|
||||
</div>
|
||||
<div class="column has-text-centered">
|
||||
<canvas id="qr-canvas" phx-hook="QrCode">
|
||||
Generating QR code...
|
||||
</canvas>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<h4 class="title is-4">
|
||||
Danger Zone
|
||||
</h4>
|
||||
|
||||
<button class="button is-danger"
|
||||
phx-click="delete_device"
|
||||
phx-value-device_id={@device.id}
|
||||
data-confirm="Are you sure? This will immediately disconnect this device and remove all associated data.">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>Delete Device <%= @device.name %></span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
@@ -6,19 +6,19 @@ defmodule FzHttpWeb.DeviceLive.Show do
|
||||
|
||||
alias FzHttp.{Devices, Users}
|
||||
|
||||
@impl true
|
||||
@impl Phoenix.LiveView
|
||||
def mount(params, session, socket) do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign_defaults(params, session, &load_data/2)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@impl Phoenix.LiveView
|
||||
def handle_params(_params, _url, socket) do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@impl Phoenix.LiveView
|
||||
def handle_event("delete_device", %{"device_id" => device_id}, socket) do
|
||||
device = Devices.get_device!(device_id)
|
||||
|
||||
@@ -46,39 +46,18 @@ defmodule FzHttpWeb.DeviceLive.Show do
|
||||
device = Devices.get_device!(id)
|
||||
|
||||
if device.user_id == socket.assigns.current_user.id do
|
||||
assign(
|
||||
socket,
|
||||
socket
|
||||
|> assign(
|
||||
device: device,
|
||||
user: Users.get_user!(device.user_id),
|
||||
page_title: device.name,
|
||||
allowed_ips: Devices.allowed_ips(device),
|
||||
dns_servers: dns_servers(device),
|
||||
dns_servers: Devices.dns_servers(device),
|
||||
endpoint: Devices.endpoint(device),
|
||||
wireguard_port: Application.fetch_env!(:fz_vpn, :wireguard_port)
|
||||
config: Devices.as_config(device)
|
||||
)
|
||||
else
|
||||
not_authorized(socket)
|
||||
end
|
||||
end
|
||||
|
||||
defp dns_servers(device) when is_struct(device) do
|
||||
dns_servers = Devices.dns_servers(device)
|
||||
|
||||
if dns_servers_empty?(dns_servers) do
|
||||
""
|
||||
else
|
||||
"DNS = #{dns_servers}"
|
||||
end
|
||||
end
|
||||
|
||||
defp dns_servers_empty?(nil), do: true
|
||||
|
||||
defp dns_servers_empty?(dns_servers) when is_binary(dns_servers) do
|
||||
len =
|
||||
dns_servers
|
||||
|> String.trim()
|
||||
|> String.length()
|
||||
|
||||
len == 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,22 +6,24 @@ defmodule FzHttpWeb.ModalComponent do
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~L"""
|
||||
<div id="<%= @myself %>" class="modal is-active"
|
||||
~H"""
|
||||
<div
|
||||
id={@myself}
|
||||
class="modal is-active"
|
||||
phx-capture-click="close"
|
||||
phx-window-keydown="close"
|
||||
phx-key="escape"
|
||||
phx-target="<%= @myself %>"
|
||||
phx-target={@myself}
|
||||
phx-page-loading>
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title"><%= @opts[:title] %></p>
|
||||
<button class="delete" aria-label="close" phx-click="close" phx-target="<%= @myself %>"></button>
|
||||
<button class="delete" aria-label="close" phx-click="close" phx-target={@myself}></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="content">
|
||||
<%= live_component(@socket, @component, @opts) %>
|
||||
<div class="block">
|
||||
<%= live_component(@component, @opts) %>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%= render FzHttpWeb.SharedView, "heading.html", page_title: @page_title %>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<div class="content">
|
||||
<div class="block">
|
||||
<p>
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
@@ -14,17 +14,17 @@
|
||||
<div class="tile is-ancestor">
|
||||
<div class="tile is-parent">
|
||||
<%= live_component(
|
||||
@socket,
|
||||
FzHttpWeb.RuleLive.RuleListComponent,
|
||||
title: "Allowlist",
|
||||
header_icon: "mdi mdi-arrow-decision-outline",
|
||||
id: :allowlist,
|
||||
current_user: @current_user) %>
|
||||
</div>
|
||||
<div class="tile is-parent">
|
||||
<%= live_component(
|
||||
@socket,
|
||||
FzHttpWeb.RuleLive.RuleListComponent,
|
||||
title: "Denylist",
|
||||
header_icon: "mdi mdi-alert-octagon",
|
||||
id: :denylist,
|
||||
current_user: @current_user) %>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,6 @@ defmodule FzHttpWeb.RuleLive.Index do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign_defaults(params, session)
|
||||
|> assign(:page_title, "Rules")}
|
||||
|> assign(:page_title, "Egress Rules")}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ defmodule FzHttpWeb.RuleLive.RuleListComponent do
|
||||
|
||||
@events_module Application.compile_env!(:fz_http, :events_module)
|
||||
|
||||
@impl true
|
||||
@impl Phoenix.LiveComponent
|
||||
def update(assigns, socket) do
|
||||
{:ok,
|
||||
socket
|
||||
@@ -34,7 +34,7 @@ defmodule FzHttpWeb.RuleLive.RuleListComponent do
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@impl Phoenix.LiveComponent
|
||||
def handle_event("delete_rule", %{"rule_id" => rule_id}, socket) do
|
||||
rule = Rules.get_rule!(rule_id)
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<div class="card tile is-child">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title"><%= @title %></p>
|
||||
<p class="card-header-title">
|
||||
<span class="icon"><i class={@header_icon}></i></span>
|
||||
<%= @title %>
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<.form let={f} for={@changeset} id={"#{@action}-form"} phx-target={@myself} phx-submit="add_rule">
|
||||
|
||||
@@ -3,47 +3,37 @@
|
||||
<section class="section is-main-section">
|
||||
<%= render FzHttpWeb.SharedView, "flash.html", assigns %>
|
||||
|
||||
<div class="tile is-ancestor is-flex-wrap-wrap">
|
||||
<div class="tile is-parent">
|
||||
<div class="card tile is-child">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">Device</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<%= live_component(
|
||||
@socket,
|
||||
FzHttpWeb.SettingLive.FormComponent,
|
||||
label_text: "Allowed IPs",
|
||||
placeholder: nil,
|
||||
changeset: @changesets["default.device.allowed_ips"],
|
||||
help_text: @help_texts.allowed_ips,
|
||||
id: :allowed_ips_form_component) %>
|
||||
<div class="block">
|
||||
<p>
|
||||
Configure Firezone-wide default settings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h4 class="title is-4">Device Defaults</h4>
|
||||
|
||||
<%= live_component(
|
||||
@socket,
|
||||
FzHttpWeb.SettingLive.FormComponent,
|
||||
label_text: "DNS Servers",
|
||||
placeholder: nil,
|
||||
changeset: @changesets["default.device.dns_servers"],
|
||||
help_text: @help_texts.dns_servers,
|
||||
id: :dns_servers_form_component) %>
|
||||
<div class="block">
|
||||
<%= live_component(
|
||||
FzHttpWeb.SettingLive.FormComponent,
|
||||
label_text: "Allowed IPs",
|
||||
placeholder: nil,
|
||||
changeset: @changesets["default.device.allowed_ips"],
|
||||
help_text: @help_texts.allowed_ips,
|
||||
id: :allowed_ips_form_component) %>
|
||||
|
||||
<hr>
|
||||
<%= live_component(
|
||||
FzHttpWeb.SettingLive.FormComponent,
|
||||
label_text: "DNS Servers",
|
||||
placeholder: nil,
|
||||
changeset: @changesets["default.device.dns_servers"],
|
||||
help_text: @help_texts.dns_servers,
|
||||
id: :dns_servers_form_component) %>
|
||||
|
||||
<%= live_component(
|
||||
@socket,
|
||||
FzHttpWeb.SettingLive.FormComponent,
|
||||
label_text: "Endpoint",
|
||||
placeholder: @endpoint_placeholder,
|
||||
changeset: @changesets["default.device.endpoint"],
|
||||
help_text: @help_texts.endpoint,
|
||||
id: :endpoint_form_component) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= live_component(
|
||||
FzHttpWeb.SettingLive.FormComponent,
|
||||
label_text: "Endpoint",
|
||||
placeholder: @endpoint_placeholder,
|
||||
changeset: @changesets["default.device.endpoint"],
|
||||
help_text: @help_texts.endpoint,
|
||||
id: :endpoint_form_component) %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div class="block">
|
||||
<.form let={f} for={@changeset} id={@id} phx-target={@myself} phx-change="change" phx-submit="save">
|
||||
<div class="field">
|
||||
<%= label f, :value, @label_text, class: "label" %>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<%= render FzHttpWeb.SharedView, "heading.html", page_title: @page_title %>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<%= render FzHttpWeb.SharedView, "flash.html", assigns %>
|
||||
|
||||
<h4 class="title is-4">Security Settings</h4>
|
||||
|
||||
<div class="block">
|
||||
<p>
|
||||
Manage security-related settings.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,14 @@
|
||||
defmodule FzHttpWeb.SettingLive.Security do
|
||||
@moduledoc """
|
||||
Manages security LiveView
|
||||
"""
|
||||
use FzHttpWeb, :live_view
|
||||
|
||||
@impl Phoenix.LiveView
|
||||
def mount(params, session, socket) do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:page_title, "Security Settings")
|
||||
|> assign_defaults(params, session)}
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
<div>
|
||||
<.form let={f} for={@changeset} id="user-form" phx-target={@myself} phx-submit="save">
|
||||
<%= if @action == :edit do %>
|
||||
<div class="content">
|
||||
<div class="block">
|
||||
<p>Change user email or enter new password below.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -12,13 +12,15 @@
|
||||
<section class="section is-main-section">
|
||||
<%= render FzHttpWeb.SharedView, "flash.html", assigns %>
|
||||
|
||||
<div class="content">
|
||||
<div class="block">
|
||||
<table class="table is-hoverable is-bordered is-striped is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="is-6">Email</th>
|
||||
<th class="is-6">Devices</th>
|
||||
<th>Email</th>
|
||||
<th>Devices</th>
|
||||
<th>Last Signed In</th>
|
||||
<th>Created</th>
|
||||
<th>Updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -33,13 +35,23 @@
|
||||
phx-hook="FormatTimestamp">
|
||||
…
|
||||
</td>
|
||||
<td id={"user-#{user.id}-inserted-at"}
|
||||
data-timestamp={user.inserted_at}
|
||||
phx-hook="FormatTimestamp">
|
||||
…
|
||||
</td>
|
||||
<td id={"user-#{user.id}-updated-at"}
|
||||
data-timestamp={user.updated_at}
|
||||
phx-hook="FormatTimestamp">
|
||||
…
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<%= live_patch(to: Routes.user_index_path(@socket, :new), class: "button is-primary") do %>
|
||||
<%= live_patch(to: Routes.user_index_path(@socket, :new), class: "button") do %>
|
||||
Add User
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
@@ -8,75 +8,55 @@
|
||||
action: @live_action) %>
|
||||
<% end %>
|
||||
|
||||
<%= render FzHttpWeb.SharedView, "heading.html", page_title: "User #{@user.email}" %>
|
||||
<%= render FzHttpWeb.SharedView, "heading.html", page_title: "Users -> #{@user.email}" %>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<%= render FzHttpWeb.SharedView, "flash.html", assigns %>
|
||||
<div class="tile is-ancestor is-flex-wrap-wrap">
|
||||
<div class="tile is-parent">
|
||||
<div class="card tile is-child">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">
|
||||
Details
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<%= render FzHttpWeb.SharedView, "user_details.html", user: @user %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="card-footer">
|
||||
<div class="field is-grouped card-footer-item">
|
||||
<p class="control">
|
||||
<%= live_patch(to: Routes.user_show_path(@socket, :edit, @user), class: "button is-primary") do %>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-edit"></i>
|
||||
</span>
|
||||
<span>Change Email or Password</span>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="control">
|
||||
<button
|
||||
class="button is-danger"
|
||||
data-confirm="Are you sure? This will permanently delete this user, all associated devices and instantly drop any active VPN sessions associated to this user."
|
||||
phx-click="delete_user"
|
||||
phx-value-user_id={@user.id}>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>Delete User</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h4 class="title is-4">Details</h4>
|
||||
</div>
|
||||
<div class="tile is-parent">
|
||||
<div class="card tile is-child">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">
|
||||
Devices
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<%= if length(@devices) > 0 do %>
|
||||
<%= render FzHttpWeb.SharedView, "devices_table.html", devices: @devices, socket: @socket %>
|
||||
<% else %>
|
||||
No devices.
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<div class="field is-grouped card-footer-item">
|
||||
<p class="control">
|
||||
<button class="button is-primary" phx-value-user_id={@user.id} phx-click="create_device">
|
||||
Add Device
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<%= live_patch(to: Routes.user_show_path(@socket, :edit, @user), class: "button") do %>
|
||||
<span class="icon is-small">
|
||||
<i class="mdi mdi-pencil"></i>
|
||||
</span>
|
||||
<span>Change Email or Password</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render FzHttpWeb.SharedView, "user_details.html", user: @user %>
|
||||
</section>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<h4 class="title is-4">Devices</h4>
|
||||
|
||||
<div class="block">
|
||||
<%= if length(@devices) > 0 do %>
|
||||
<%= render FzHttpWeb.SharedView, "devices_table.html", devices: @devices, socket: @socket %>
|
||||
<% else %>
|
||||
No devices.
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<button class="button" phx-value-user_id={@user.id} phx-click="create_device">
|
||||
Add Device
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="section is-main-section">
|
||||
<h4 class="title is-4">Danger Zone</h4>
|
||||
|
||||
<button
|
||||
class="button is-danger"
|
||||
data-confirm="Are you sure? This will permanently delete this user, all associated devices and instantly drop any active VPN sessions associated to this user."
|
||||
phx-click="delete_user"
|
||||
phx-value-user_id={@user.id}>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<span>Delete User</span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
@@ -22,6 +22,7 @@ defmodule FzHttpWeb.Router do
|
||||
pipe_through :browser
|
||||
|
||||
get "/", DeviceController, :index
|
||||
get "/devices/:id/dl", DeviceController, :download_config
|
||||
resources "/session", SessionController, only: [:new, :create, :delete], singleton: true
|
||||
|
||||
live "/users", UserLive.Index, :index
|
||||
@@ -37,10 +38,12 @@ defmodule FzHttpWeb.Router do
|
||||
|
||||
live "/settings/default", SettingLive.Default, :default
|
||||
|
||||
live "/account", AccountLive.Show, :show
|
||||
live "/account/edit", AccountLive.Show, :edit
|
||||
live "/settings/security", SettingLive.Security, :security
|
||||
|
||||
live "/connectivity_checks", ConnectivityCheckLive.Index, :index
|
||||
live "/settings/account", AccountLive.Show, :show
|
||||
live "/settings/account/edit", AccountLive.Show, :edit
|
||||
|
||||
live "/diagnostics/connectivity_checks", ConnectivityCheckLive.Index, :index
|
||||
|
||||
get "/sign_in/:token", SessionController, :create
|
||||
delete "/user", UserController, :delete
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= if !is_nil(get_flash(@conn, :info)) or !is_nil(get_flash(@conn, :error)) do %>
|
||||
<div class="container flash-squeeze">
|
||||
<div class="block flash-squeeze">
|
||||
<%= if get_flash(@conn, :info) do %>
|
||||
<div class="notification is-info">
|
||||
<button title="Dismiss notification" class="delete"></button>
|
||||
|
||||
@@ -15,15 +15,20 @@
|
||||
</head>
|
||||
<body>
|
||||
<section class="section hero is-fullheight is-error-section">
|
||||
<div id="app" class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column is-4 is-offset-4">
|
||||
<%= @inner_content %>
|
||||
<div id="app" class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-two-thirds-tablet is-half-desktop is-one-third-widescreen">
|
||||
<div class="block">
|
||||
<div class="has-text-centered">
|
||||
<img src={Routes.static_path(@conn, "/images/logo-text.svg")} alt="firez.one">
|
||||
</div>
|
||||
</div>
|
||||
<%= @inner_content %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html lang="en" class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<%= csrf_meta_tag() %>
|
||||
<%= live_title_tag assigns[:page_title], prefix: "Firezone • " %>
|
||||
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/css/app.css")} />
|
||||
<script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/js/app.js")}></script>
|
||||
@@ -17,6 +16,15 @@
|
||||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||||
<meta name="msapplication-TileColor" content="331700">
|
||||
<meta name="theme-color" content="331700">
|
||||
|
||||
<!-- User Socket -->
|
||||
<%= tag :meta, name: "user-token", content: Phoenix.Token.sign(@conn, "user auth", @current_user.id) %>
|
||||
|
||||
<!-- Notification Channel -->
|
||||
<%= tag :meta, name: "channel-token", content: Phoenix.Token.sign(@conn, "channel auth", @current_user.id) %>
|
||||
|
||||
<!-- CSRF -->
|
||||
<%= csrf_meta_tag() %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
@@ -67,20 +75,20 @@
|
||||
<p class="menu-label">Configuration</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<%= link(to: Routes.user_index_path(@conn, :index), class: nav_class(@conn.request_path, "users")) do %>
|
||||
<%= link(to: Routes.user_index_path(@conn, :index), class: nav_class(@conn, ~r"/users")) do %>
|
||||
<span class="icon"><i class="mdi mdi-account-group"></i></span>
|
||||
<span class="menu-item-label">Users</span>
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link(to: Routes.device_index_path(@conn, :index), class: nav_class(@conn.request_path, "devices")) do %>
|
||||
<%= link(to: Routes.device_index_path(@conn, :index), class: nav_class(@conn, ~r"/devices")) do %>
|
||||
<span class="icon"><i class="mdi mdi-laptop"></i></span>
|
||||
<span class="menu-item-label">Devices</span>
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link(to: Routes.rule_index_path(@conn, :index), class: nav_class(@conn.request_path, "rules")) do %>
|
||||
<span class="icon"><i class="mdi mdi-plus-network"></i></span>
|
||||
<%= link(to: Routes.rule_index_path(@conn, :index), class: nav_class(@conn, ~r"/rules")) do %>
|
||||
<span class="icon"><i class="mdi mdi-traffic-light"></i></span>
|
||||
<span class="menu-item-label">Rules</span>
|
||||
<% end %>
|
||||
</li>
|
||||
@@ -88,24 +96,32 @@
|
||||
<p class="menu-label">Settings</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<%= link(to: Routes.setting_default_path(@conn, :default), class: nav_class(@conn.request_path, "defaults")) do %>
|
||||
<%= link(to: Routes.setting_default_path(@conn, :default), class: nav_class(@conn, ~r"/settings/default")) do %>
|
||||
<span class="icon"><i class="mdi mdi-cog"></i></span>
|
||||
<span class="menu-item-label">Defaults</span>
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link(to: Routes.account_show_path(@conn, :show), class: nav_class(@conn.request_path, "account")) do %>
|
||||
<%= link(to: Routes.account_show_path(@conn, :show), class: nav_class(@conn, ~r"/settings/account")) do %>
|
||||
<span class="icon"><i class="mdi mdi-account"></i></span>
|
||||
<span class="menu-item-label">Account</span>
|
||||
<% end %>
|
||||
</li>
|
||||
<!-- XXX: Future settings pane
|
||||
<li>
|
||||
<%= link(to: Routes.setting_security_path(@conn, :security), class: nav_class(@conn, ~r"/settings/security")) do %>
|
||||
<span class="icon"><i class="mdi mdi-lock"></i></span>
|
||||
<span class="menu-item-label">Security</span>
|
||||
<% end %>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
<p class="menu-label">Diagnostics</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<%= link(to: Routes.connectivity_check_index_path(@conn, :index), class: nav_class(@conn.request_path, "connectivity_checks")) do %>
|
||||
<%= link(to: Routes.connectivity_check_index_path(@conn, :index), class: nav_class(@conn, ~r"/diagnostics/connectivity_checks")) do %>
|
||||
<span class="icon"><i class="mdi mdi-access-point"></i></span>
|
||||
<span class="menu-item-label">Connectivity Checks</span>
|
||||
<span class="menu-item-label">WAN Connectivity</span>
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -120,7 +136,7 @@
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<%= link(to: "mailto:" <> feedback_recipient()) do %>
|
||||
Click here to leave feedback
|
||||
Leave us feedback!
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<!-- Use https://admin-one.justboil.me/?style=light-dark#/lock-screen for inspiration -->
|
||||
<h3 class="title">Sign In</h3>
|
||||
<h3 class="is-3 title">Sign In</h3>
|
||||
|
||||
<hr>
|
||||
|
||||
<%= form_for @changeset, Routes.session_path(@conn, :create), fn f -> %>
|
||||
<%= if assigns[:changeset] && @changeset.action do %>
|
||||
<div>
|
||||
<div class="block">
|
||||
<p>Oops, something went wrong! Please check the errors below.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -31,7 +32,7 @@
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<%= submit "Sign in", class: "button is-primary" %>
|
||||
<%= submit "Sign In", class: "button" %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -4,16 +4,20 @@
|
||||
<th>Name</th>
|
||||
<th>WireGuard IP</th>
|
||||
<th>Public key</th>
|
||||
<th>Created</th>
|
||||
<th>Updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for device <- @devices do %>
|
||||
<tr>
|
||||
<td>
|
||||
<%= link(device.name, to: Routes.device_show_path(@socket, :show, device)) %>
|
||||
<%= live_patch(device.name, to: Routes.device_show_path(@socket, :show, device)) %>
|
||||
</td>
|
||||
<td class="code"><%= FzHttp.Devices.ipv4_address(device) %>, <%= FzHttp.Devices.ipv6_address(device) %></td>
|
||||
<td class="code"><%= device.public_key %></td>
|
||||
<td id={"device-#{device.id}-inserted-at"} data-timestamp={device.inserted_at} phx-hook="FormatTimestamp">…</td>
|
||||
<td id={"device-#{device.id}-updated-at"} data-timestamp={device.updated_at} phx-hook="FormatTimestamp">…</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
|
||||
@@ -1,14 +1,43 @@
|
||||
<dl>
|
||||
<dt>
|
||||
<strong>Email</strong>
|
||||
</dt>
|
||||
<dd><%= @user.email %></dd>
|
||||
<table class="table is-bordered is-hoverable is-striped is-fullwidth">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Email</strong></td>
|
||||
<td><%= @user.email %></td>
|
||||
</tr>
|
||||
|
||||
<dt><strong>Last signed in</strong></dt>
|
||||
<dd
|
||||
id="last-signed-in-at"
|
||||
data-timestamp={@user.last_signed_in_at}
|
||||
phx-hook="FormatTimestamp">
|
||||
…
|
||||
</dd>
|
||||
</dl>
|
||||
<tr>
|
||||
<td><strong>Last Signed In</strong></td>
|
||||
<td
|
||||
id="last-signed-in-at"
|
||||
data-timestamp={@user.last_signed_in_at}
|
||||
phx-hook="FormatTimestamp">
|
||||
…
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Created</strong></td>
|
||||
<td
|
||||
id="created-at"
|
||||
data-timestamp={@user.inserted_at}
|
||||
phx-hook="FormatTimestamp">
|
||||
…
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Updated</strong></td>
|
||||
<td
|
||||
id="created-at"
|
||||
data-timestamp={@user.updated_at}
|
||||
phx-hook="FormatTimestamp">
|
||||
…
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><strong>Number of Devices</strong></td>
|
||||
<td><%= FzHttp.Devices.count(@user.id) %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -30,19 +30,15 @@ defmodule FzHttpWeb.LayoutView do
|
||||
@doc """
|
||||
Generate class for nav links
|
||||
"""
|
||||
def nav_class(request_path, section) do
|
||||
top_level =
|
||||
request_path
|
||||
|> String.split("/", trim: true)
|
||||
|> List.first("devices")
|
||||
def nav_class(%{request_path: "/"} = _conn, ~r"devices") do
|
||||
"is-active has-icon"
|
||||
end
|
||||
|
||||
active =
|
||||
if top_level == section do
|
||||
"is-active"
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
Enum.join([active, "has-icon"], " ")
|
||||
def nav_class(%{request_path: request_path} = _conn, regex) do
|
||||
if String.match?(request_path, regex) do
|
||||
"is-active has-icon"
|
||||
else
|
||||
"has-icon"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -67,9 +67,9 @@ defmodule FzHttp.MixProject do
|
||||
{:inflex, "~> 2.1"},
|
||||
{:plug, "~> 1.12.1"},
|
||||
{:postgrex, "~> 0.15.10"},
|
||||
{:phoenix_html, "~> 3.0.3"},
|
||||
{:phoenix_html, "~> 3.1.0"},
|
||||
{:phoenix_live_reload, "~> 1.3", only: :dev},
|
||||
{:phoenix_live_view, "~> 0.16.3"},
|
||||
{:phoenix_live_view, "~> 0.17.5"},
|
||||
{:gettext, "~> 0.18"},
|
||||
{:jason, "~> 1.2"},
|
||||
{:telemetry, "~> 0.4.3"},
|
||||
|
||||
@@ -62,7 +62,7 @@ defmodule FzHttp.ConnectivityChecksTest do
|
||||
response_body: "some updated response_body",
|
||||
response_code: 500,
|
||||
response_headers: %{"updated" => "response headers"},
|
||||
url: "https://ping.firez.one/6.6.6"
|
||||
url: "https://ping-dev.firez.one/6.6.6"
|
||||
}
|
||||
|
||||
assert {:ok, %ConnectivityCheck{} = connectivity_check} =
|
||||
@@ -71,7 +71,7 @@ defmodule FzHttp.ConnectivityChecksTest do
|
||||
assert connectivity_check.response_body == "some updated response_body"
|
||||
assert connectivity_check.response_code == 500
|
||||
assert connectivity_check.response_headers == %{"updated" => "response headers"}
|
||||
assert connectivity_check.url == "https://ping.firez.one/6.6.6"
|
||||
assert connectivity_check.url == "https://ping-dev.firez.one/6.6.6"
|
||||
end
|
||||
|
||||
test "update_connectivity_check/2 with invalid data returns error changeset" do
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
defmodule FzHttpWeb.NotificationChannelTest do
|
||||
use FzHttpWeb.ChannelCase, async: true
|
||||
|
||||
alias FzHttp.UsersFixtures
|
||||
alias FzHttpWeb.NotificationChannel
|
||||
|
||||
describe "channel join" do
|
||||
setup _tags do
|
||||
user = UsersFixtures.user()
|
||||
|
||||
socket =
|
||||
FzHttpWeb.UserSocket
|
||||
|> socket(user.id, %{remote_ip: "127.0.0.1"})
|
||||
|
||||
%{
|
||||
user: user,
|
||||
socket: socket,
|
||||
token: Phoenix.Token.sign(socket, "channel auth", user.id)
|
||||
}
|
||||
end
|
||||
|
||||
test "joins channel with valid token", %{token: token, socket: socket, user: user} do
|
||||
payload = %{
|
||||
"token" => token,
|
||||
"user_agent" => "test"
|
||||
}
|
||||
|
||||
{:ok, _, test_socket} =
|
||||
socket
|
||||
|> subscribe_and_join(NotificationChannel, "notification:session", payload)
|
||||
|
||||
assert test_socket.assigns.current_user.id == user.id
|
||||
end
|
||||
|
||||
test "prevents joining with invalid token", %{token: _token, socket: socket, user: _user} do
|
||||
payload = %{
|
||||
"token" => "foobar",
|
||||
"user_agent" => "test"
|
||||
}
|
||||
|
||||
assert {:error, %{reason: "unauthorized"}} ==
|
||||
socket
|
||||
|> subscribe_and_join(NotificationChannel, "notification:session", payload)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -41,7 +41,7 @@ defmodule FzHttpWeb.DeviceLive.ShowTest do
|
||||
path = Routes.device_show_path(conn, :show, device)
|
||||
{:ok, _view, html} = live(conn, path)
|
||||
assert html =~ "#{device.name}"
|
||||
assert html =~ "<p class=\"card-header-title\">Details</p>"
|
||||
assert html =~ "<h4 class=\"title is-4\">Details</h4>"
|
||||
end
|
||||
|
||||
test "opens modal", %{authed_conn: conn, device: device} do
|
||||
@@ -220,7 +220,7 @@ defmodule FzHttpWeb.DeviceLive.ShowTest do
|
||||
{:ok, view, _html} = live(conn, path)
|
||||
|
||||
view
|
||||
|> element("button", "Delete")
|
||||
|> element("button", "Delete Device #{device.name}")
|
||||
|> render_click()
|
||||
|
||||
_flash = assert_redirected(view, Routes.device_index_path(conn, :index))
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
defmodule FzHttpWeb.SettingLive.SecurityTest do
|
||||
use FzHttpWeb.ConnCase, async: true
|
||||
|
||||
describe "authenticated mount" do
|
||||
test "loads the active sessions table", %{authed_conn: conn} do
|
||||
path = Routes.setting_security_path(conn, :security)
|
||||
{:ok, _view, html} = live(conn, path)
|
||||
|
||||
assert html =~ "<h4 class=\"title is-4\">Security Settings</h4>"
|
||||
end
|
||||
end
|
||||
|
||||
describe "unauthenticated mount" do
|
||||
test "redirects to not authorized", %{unauthed_conn: conn} do
|
||||
path = Routes.setting_security_path(conn, :security)
|
||||
expected_path = Routes.session_path(conn, :new)
|
||||
|
||||
assert {:error, {:redirect, %{to: ^expected_path}}} = live(conn, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,15 +9,16 @@ defmodule FzHttpWeb.LayoutViewTest do
|
||||
# import Phoenix.HTML
|
||||
describe "nav_class/2" do
|
||||
test "it computes nav class for root route" do
|
||||
assert LayoutView.nav_class("/", "devices") == "is-active has-icon"
|
||||
assert LayoutView.nav_class(%{request_path: "/"}, ~r"devices") == "is-active has-icon"
|
||||
end
|
||||
|
||||
test "it computes nav class for account route" do
|
||||
assert LayoutView.nav_class("/account", "account") == "is-active has-icon"
|
||||
assert LayoutView.nav_class(%{request_path: "/account"}, ~r"account") ==
|
||||
"is-active has-icon"
|
||||
end
|
||||
|
||||
test "it defaults to has-icon" do
|
||||
assert LayoutView.nav_class("Blah", "foo") == " has-icon"
|
||||
assert LayoutView.nav_class(%{request_path: "Blah"}, ~r"foo") == "has-icon"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@ defmodule FzHttpWeb.ChannelCase do
|
||||
using do
|
||||
quote do
|
||||
# Import conveniences for testing with channels
|
||||
use Phoenix.ChannelTest
|
||||
import Phoenix.ChannelTest
|
||||
import FzHttp.TestHelpers
|
||||
|
||||
# The default endpoint for testing
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule FzHttp.ConnectivityChecksFixtures do
|
||||
response_body: "some response_body",
|
||||
response_code: 142,
|
||||
response_headers: %{"Content-Type" => "text/plain"},
|
||||
url: "https://ping.firez.one/0.0.0+git.0.deadbeef0"
|
||||
url: "https://ping-dev.firez.one/0.0.0+git.0.deadbeef0"
|
||||
})
|
||||
|> ConnectivityChecks.create_connectivity_check()
|
||||
|
||||
|
||||
8
mix.lock
8
mix.lock
@@ -14,7 +14,7 @@
|
||||
"db_connection": {:hex, :db_connection, "2.4.1", "6411f6e23f1a8b68a82fa3a36366d4881f21f47fc79a9efb8c615e62050219da", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ea36d226ec5999781a9a8ad64e5d8c4454ecedc7a4d643e4832bf08efca01f00"},
|
||||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.17", "6f3c7e94170377ba45241d394389e800fb15adc5de51d0a3cd52ae766aafd63f", [:mix], [], "hexpm", "f93ac89c9feca61c165b264b5837bf82344d13bebc634cd575cb711e2e342023"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.18", "e1b2be73eb08a49fb032a0208bf647380682374a725dfb5b9e510def8397f6f2", [:mix], [], "hexpm", "114a0e85ec3cf9e04b811009e73c206394ffecfcc313e0b346de0d557774ee97"},
|
||||
"ecto": {:hex, :ecto, "3.7.1", "a20598862351b29f80f285b21ec5297da1181c0442687f9b8329f0445d228892", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d36e5b39fc479e654cffd4dbe1865d9716e4a9b6311faff799b6f90ab81b8638"},
|
||||
"ecto_network": {:hex, :ecto_network, "1.3.0", "1e77fa37c20e0f6a426d3862732f3317b0fa4c18f123d325f81752a491d7304e", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.14.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "053a5e46ef2837e8ea5ea97c82fa0f5494699209eddd764e663c85f11b2865bd"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.7.1", "8de624ef50b2a8540252d8c60506379fbbc2707be1606853df371cf53df5d053", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b42a32e2ce92f64aba5c88617891ab3b0ba34f3f3a503fa20009eae1a401c81"},
|
||||
@@ -41,11 +41,11 @@
|
||||
"mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
|
||||
"phoenix": {:hex, :phoenix, "1.6.2", "6cbd5c8ed7a797f25a919a37fafbc2fb1634c9cdb12a4448d7a5d0b26926f005", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7bbee475acae0c3abc229b7f189e210ea788e63bd168e585f60c299a4b2f9133"},
|
||||
"phoenix": {:hex, :phoenix, "1.6.4", "bc9a757f0a4eac88e1e3501245a6259e74d30970df8c072836d755608dbc4c7d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b6cb3f31e3ea1049049852703eca794f7afdb0c1dc111d8f166ba032c103a80"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.0.4", "232d41884fe6a9c42d09f48397c175cd6f0d443aaa34c7424da47604201df2e1", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ce17fd3cf815b2ed874114073e743507704b1f5288bb03c304a77458485efc8b"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.16.4", "5692edd0bac247a9a816eee7394e32e7a764959c7d0cf9190662fc8b0cd24c97", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "754ba49aa2e8601afd4f151492c93eb72df69b0b9856bab17711b8397e43bba0"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.17.5", "63f52a6f9f6983f04e424586ff897c016ecc5e4f8d1e2c22c2887af1c57215d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c5586e6a3d4df71b8214c769d4f5eb8ece2b4001711a7ca0f97323c36958b0e3"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||
"phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
|
||||
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
||||
|
||||
Reference in New Issue
Block a user