Generate API docs from tests and Phoenix Controller docs (#1286)

1. The test data is taken from tests, you can override attributes by
adding keyword params to `doc` macro;
2. Additionally, you can add a section title using a `@moduledoc` in a
controller and a controller action title using `@doc` on a controller
function. (It will be added to all instances of its usage).
3. To make parameters nice a helper was added to build it using
`DocHelper`, you can find an example in UserController.

Overall, the code is messy, I'll need to revisit it, but was doing it in
a rush so hope it's good enough for v0.

Results you can see at https://firezone.docs.apiary.io/.

To generate it locally run `DOC=1 mix test
test/fz_http_web/controllers/json`.

Co-authored-by: Jamil Bou Kheir <jamilbk@users.noreply.github.com>
This commit is contained in:
Andrew Dryga
2023-01-13 00:40:21 -06:00
committed by GitHub
parent 05ad3f3239
commit 6003ea7e26
27 changed files with 1566 additions and 1801 deletions

View File

@@ -1,3 +1,3 @@
[codespell]
skip = ./erl_crash.dump,./apps/fz_http/erl_crash.dump,./cover,./vendor,./omnibus,*.json,yarn.lock,seeds.exs,./**/node_modules,./deps,./priv/static,./priv/plts,./**/priv/static,./.git,./docs/build,./_build
skip = ./docs/docs/reference/api/*.mdx,./erl_crash.dump,./apps/fz_http/erl_crash.dump,./cover,./vendor,./omnibus,*.json,yarn.lock,seeds.exs,./**/node_modules,./deps,./priv/static,./priv/plts,./**/priv/static,./.git,./docs/build,./_build
ignore-words-list = keypair,keypairs,iif,statics,wee

View File

@@ -17,13 +17,10 @@ jobs:
node-version: 16
cache: 'yarn'
cache-dependency-path: docs/yarn.lock
# XXX: Add API docs generation here
- run: |
cd docs/
yarn install --frozen-lockfile
# Uncomment below when REST API docs are done
# yarn run docusaurus gen-api-docs rest_api
yarn build
- name: Publish Latest Docs
uses: JamesIves/github-pages-deploy-action@v4.4.1

View File

@@ -25,6 +25,7 @@ defmodule FzHttpWeb do
import FzHttpWeb.Gettext
import Phoenix.LiveView.Controller
import FzHttpWeb.ControllerHelpers
import FzHttpWeb.DocHelpers
unquote(verified_routes())
end

View File

@@ -1,15 +1,21 @@
defmodule FzHttpWeb.JSON.ConfigurationController do
@moduledoc api_doc: [title: "Configurations", group: "Configuration"]
@moduledoc """
This endpoint allows an administrator to manage Configurations.
"""
use FzHttpWeb, :controller
alias FzHttp.{Configurations.Configuration, Configurations}
action_fallback FzHttpWeb.JSON.FallbackController
action_fallback(FzHttpWeb.JSON.FallbackController)
@doc api_doc: [summary: "Get Configuration"]
def show(conn, _params) do
configuration = Configurations.get_configuration!()
render(conn, "show.json", configuration: configuration)
end
@doc api_doc: [summary: "Update Configuration"]
def update(conn, %{"configuration" => params}) do
configuration = Configurations.get_configuration!()

View File

@@ -1,19 +1,22 @@
defmodule FzHttpWeb.JSON.DeviceController do
@moduledoc api_doc: [title: "Devices", group: "Devices"]
@moduledoc """
REST API Controller for Devices.
This endpoint allows an administrator to manage Devices.
"""
use FzHttpWeb, :controller
action_fallback FzHttpWeb.JSON.FallbackController
action_fallback(FzHttpWeb.JSON.FallbackController)
alias FzHttp.Devices
@doc api_doc: [summary: "List all Devices"]
def index(conn, _params) do
devices = Devices.list_devices()
render(conn, "index.json", devices: devices)
end
@doc api_doc: [summary: "Create a Device"]
def create(conn, %{"device" => device_params}) do
with {:ok, device} <- Devices.create_device(device_params) do
conn
@@ -23,11 +26,13 @@ defmodule FzHttpWeb.JSON.DeviceController do
end
end
@doc api_doc: [summary: "Get Device by ID"]
def show(conn, %{"id" => id}) do
device = Devices.get_device!(id)
render(conn, "show.json", device: device)
end
@doc api_doc: [summary: "Update a Device"]
def update(conn, %{"id" => id, "device" => device_params}) do
device = Devices.get_device!(id)
@@ -36,6 +41,7 @@ defmodule FzHttpWeb.JSON.DeviceController do
end
end
@doc api_doc: [summary: "Delete a Device"]
def delete(conn, %{"id" => id}) do
device = Devices.get_device!(id)

View File

@@ -1,20 +1,22 @@
defmodule FzHttpWeb.JSON.RuleController do
@moduledoc api_doc: [title: "Rules", group: "Rules"]
@moduledoc """
REST API Controller for Rules.
This endpoint allows an adminisrator to manage Rules.
"""
use FzHttpWeb, :controller
action_fallback FzHttpWeb.JSON.FallbackController
action_fallback(FzHttpWeb.JSON.FallbackController)
alias FzHttp.Rules
@doc api_doc: [summary: "List all Rules"]
def index(conn, _params) do
# XXX: Add user-scoped rules
rules = Rules.list_rules()
render(conn, "index.json", rules: rules)
end
@doc api_doc: [summary: "Create a Rule"]
def create(conn, %{"rule" => rule_params}) do
with {:ok, rule} <- Rules.create_rule(rule_params) do
conn
@@ -24,11 +26,13 @@ defmodule FzHttpWeb.JSON.RuleController do
end
end
@doc api_doc: [summary: "Get Rule by ID"]
def show(conn, %{"id" => id}) do
rule = Rules.get_rule!(id)
render(conn, "show.json", rule: rule)
end
@doc api_doc: [summary: "Update a Rule"]
def update(conn, %{"id" => id, "rule" => rule_params}) do
rule = Rules.get_rule!(id)
@@ -37,6 +41,7 @@ defmodule FzHttpWeb.JSON.RuleController do
end
end
@doc api_doc: [summary: "Delete a Rule"]
def delete(conn, %{"id" => id}) do
rule = Rules.get_rule!(id)

View File

@@ -1,16 +1,54 @@
defmodule FzHttpWeb.JSON.UserController do
use FzHttpWeb, :controller
@moduledoc api_doc: [title: "Users", sidebar_position: 2, toc_max_heading_level: 4]
@moduledoc """
This endpoint allows an administrator to manage Users.
## Auto-Create Users from OpenID or SAML providers
You can set Configuration option `auto_create_users` to `true` to automatically create users
from OpenID or SAML providers. Use it with care as anyone with access to the provider will be
able to log-in to Firezone.
If `auto_create_users` is `false`, then you need to provision users with `password` attribute,
otherwise they will have no means to log in.
"""
use FzHttpWeb, :controller
alias FzHttp.Users
alias FzHttp.Users.User
action_fallback FzHttpWeb.JSON.FallbackController
action_fallback(FzHttpWeb.JSON.FallbackController)
@doc api_doc: [action: "List all Users"]
def index(conn, _params) do
users = Users.list_users()
render(conn, "index.json", users: users)
end
@doc """
Create a new User.
This endpoint is useful in two cases:
1. When [Local Authentication](/authenticate/local-auth/) is enabled (discouraged in
production deployments), it allows an administrator to provision users with their passwords;
2. When `auto_create_users` in the associated OpenID or SAML configuration is disabled,
it allows an administrator to provision users with their emails beforehand, effectively
whitelisting specific users for authentication.
If `auto_create_users` is `true` in the associated OpenID or SAML configuration, there is no need
to provision users; they will be created automatically when they log in for the first time using
the associated OpenID or SAML provider.
#### User Attributes
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `role` | `admin` or `unprivileged` (default) | No | User role. |
| `email` | `string` | Yes | Email which will be used to identify the user. |
| `password` | `string` | No | A password that can be used for login-password authentication. |
| `password_confirmation` | `string` | -> | Is required when the `password` is set. |
"""
@doc api_doc: [action: "Create a User"]
def create(conn, %{"user" => %{"role" => "admin"} = user_params}) do
with {:ok, %User{} = user} <- Users.create_admin_user(user_params) do
conn
@@ -29,11 +67,16 @@ defmodule FzHttpWeb.JSON.UserController do
end
end
@doc api_doc: [summary: "Get User by ID or Email"]
def show(conn, %{"id" => id_or_email}) do
user = get_user_by_id_or_email(id_or_email)
render(conn, "show.json", user: user)
end
@doc """
For details please see [Create a User](#create-a-user-post-v0users) section.
"""
@doc api_doc: [action: "Update a User"]
def update(conn, %{"id" => id_or_email, "user" => user_params}) do
user = get_user_by_id_or_email(id_or_email)
@@ -42,6 +85,7 @@ defmodule FzHttpWeb.JSON.UserController do
end
end
@doc api_doc: [summary: "Delete a User"]
def delete(conn, %{"id" => id_or_email}) do
user = get_user_by_id_or_email(id_or_email)

View File

@@ -0,0 +1,17 @@
defmodule FzHttpWeb.DocHelpers do
def group(name, children) do
{:group, {name, children}}
end
def attr(name, type, opts) do
required? = Keyword.get(opts, :required?, false)
description = Keyword.get(opts, :description)
{:attr, {name, type, required?, description}}
end
def type(type), do: {:type, type}
def type(type, example), do: {:type, {type, example}}
def enum_type(type, values, example \\ nil),
do: {:type, {:enum, type, values, example || List.first(values)}}
end

View File

@@ -95,7 +95,8 @@ defmodule FzHttp.MixProject do
{:telemetry, "~> 1.0"},
{:plug_cowboy, "~> 2.5"},
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
{:remote_ip, "~> 1.0"}
{:remote_ip, "~> 1.0"},
{:bureaucrat, "~> 0.2.9", only: :test}
]
end

View File

@@ -4,7 +4,10 @@ defmodule FzHttpWeb.JSON.ConfigurationControllerTest do
describe "GET /v0/configuration" do
test "renders configuration" do
conn = get(authed_conn(), ~p"/v0/configuration")
conn =
get(authed_conn(), ~p"/v0/configuration")
|> doc()
assert json_response(conn, 200)["data"]
end
@@ -98,7 +101,10 @@ defmodule FzHttpWeb.JSON.ConfigurationControllerTest do
}
test "updates fields when data is valid" do
conn = put(authed_conn(), ~p"/v0/configuration", configuration: @first_config)
conn =
put(authed_conn(), ~p"/v0/configuration", configuration: @first_config)
|> doc()
assert @first_config = json_response(conn, 200)["data"]
conn = put(authed_conn(), ~p"/v0/configuration", configuration: @second_config)

View File

@@ -25,14 +25,18 @@ defmodule FzHttpWeb.JSON.DeviceControllerTest do
describe "GET /v0/devices/:id" do
test "shows device" do
id = device().id
conn = get(authed_conn(), ~p"/v0/devices/#{id}")
conn =
get(authed_conn(), ~p"/v0/devices/#{id}")
|> doc()
assert %{"id" => ^id} = json_response(conn, 200)["data"]
end
test "renders 404 for device not found" do
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
get(authed_conn(), ~p"/v0/devices/003da73d-2dd9-4492-8136-3282843545e8")
end
end)
end
test "renders 401 for missing authorization header" do
@@ -52,6 +56,7 @@ defmodule FzHttpWeb.JSON.DeviceControllerTest do
post(authed_conn(), ~p"/v0/devices",
device: Map.merge(@params, %{"user_id" => unprivileged_user.id})
)
|> doc()
assert @params = json_response(conn, 201)["data"]
end
@@ -84,11 +89,14 @@ defmodule FzHttpWeb.JSON.DeviceControllerTest do
end
end
describe "[authed] PUT /v0/devices/:id" do
describe "PUT /v0/devices/:id" do
test "updates device" do
device = device()
conn = put(authed_conn(), ~p"/v0/devices/#{device}", device: @params)
conn =
put(authed_conn(), ~p"/v0/devices/#{device}", device: @params)
|> doc()
assert @params = json_response(conn, 200)["data"]
conn = get(conn, ~p"/v0/devices/#{device}")
@@ -96,9 +104,9 @@ defmodule FzHttpWeb.JSON.DeviceControllerTest do
end
test "renders 404 for device not found" do
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
put(authed_conn(), ~p"/v0/devices/003da73d-2dd9-4492-8136-3282843545e8", device: %{})
end
end)
end
test "renders 401 for missing authorization header" do
@@ -111,7 +119,9 @@ defmodule FzHttpWeb.JSON.DeviceControllerTest do
test "lists all devices" do
devices = for _i <- 1..5, do: device()
conn = get(authed_conn(), ~p"/v0/devices")
conn =
get(authed_conn(), ~p"/v0/devices")
|> doc()
assert json_response(conn, 200)["data"]
|> Enum.map(& &1["id"])
@@ -131,18 +141,21 @@ defmodule FzHttpWeb.JSON.DeviceControllerTest do
test "deletes device" do
device = device()
conn = delete(authed_conn(), ~p"/v0/devices/#{device}")
conn =
delete(authed_conn(), ~p"/v0/devices/#{device}")
|> doc()
assert response(conn, 204)
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
get(conn, ~p"/v0/devices/#{device}")
end
end)
end
test "renders 404 for device not found" do
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
delete(authed_conn(), ~p"/v0/devices/003da73d-2dd9-4492-8136-3282843545e8")
end
end)
end
test "renders 401 for missing authorization header" do

View File

@@ -22,14 +22,18 @@ defmodule FzHttpWeb.JSON.RuleControllerTest do
describe "GET /v0/rules/:id" do
test "shows rule" do
id = rule().id
conn = get(authed_conn(), ~p"/v0/rules/#{id}")
conn =
get(authed_conn(), ~p"/v0/rules/#{id}")
|> doc()
assert %{"id" => ^id} = json_response(conn, 200)["data"]
end
test "renders 404 for rule not found" do
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
get(authed_conn(), ~p"/v0/rules/003da73d-2dd9-4492-8136-3282843545e8")
end
end)
end
test "renders 401 for missing authorization header" do
@@ -46,6 +50,7 @@ defmodule FzHttpWeb.JSON.RuleControllerTest do
conn =
post(conn, ~p"/v0/rules", rule: Map.merge(@accept_rule_params, %{"user_id" => user.id}))
|> doc()
assert @accept_rule_params = json_response(conn, 201)["data"]
end
@@ -79,7 +84,11 @@ defmodule FzHttpWeb.JSON.RuleControllerTest do
describe "PUT /v0/rules/:id" do
test "updates accept rule when valid" do
rule = rule()
conn = put(authed_conn(), ~p"/v0/rules/#{rule}", rule: @accept_rule_params)
conn =
put(authed_conn(), ~p"/v0/rules/#{rule}", rule: @accept_rule_params)
|> doc()
assert @accept_rule_params = json_response(conn, 200)["data"]
conn = get(conn, ~p"/v0/rules/#{rule}")
@@ -103,9 +112,9 @@ defmodule FzHttpWeb.JSON.RuleControllerTest do
end
test "renders 404 for rule not found" do
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
put(authed_conn(), ~p"/v0/rules/003da73d-2dd9-4492-8136-3282843545e8", rule: %{})
end
end)
end
test "renders 401 for missing authorization header" do
@@ -117,7 +126,10 @@ defmodule FzHttpWeb.JSON.RuleControllerTest do
describe "GET /v0/rules" do
test "lists rules" do
for i <- 1..5, do: rule(%{destination: "10.3.2.#{i}"})
conn = get(authed_conn(), ~p"/v0/rules")
conn =
get(authed_conn(), ~p"/v0/rules")
|> doc()
actual =
Rules.list_rules()
@@ -141,18 +153,22 @@ defmodule FzHttpWeb.JSON.RuleControllerTest do
describe "DELETE /v0/rules/:id" do
test "deletes rule" do
rule = rule()
conn = delete(authed_conn(), ~p"/v0/rules/#{rule}")
conn =
delete(authed_conn(), ~p"/v0/rules/#{rule}")
|> doc()
assert response(conn, 204)
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
get(authed_conn(), ~p"/v0/rules/#{rule}")
end
end)
end
test "renders 404 for rule not found" do
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
delete(authed_conn(), ~p"/v0/rules/003da73d-2dd9-4492-8136-3282843545e8")
end
end)
end
test "renders 401 for missing authorization header" do

View File

@@ -20,9 +20,11 @@ defmodule FzHttpWeb.JSON.UserControllerTest do
describe "GET /v0/users" do
test "lists all users" do
for _i <- 1..5, do: user()
for _i <- 1..3, do: user()
conn = get(authed_conn(), ~p"/v0/users")
conn =
get(authed_conn(), ~p"/v0/users")
|> doc()
actual =
Users.list_users()
@@ -39,20 +41,44 @@ defmodule FzHttpWeb.JSON.UserControllerTest do
test "renders 401 for missing authorization header" do
conn = get(unauthed_conn(), ~p"/v0/users")
assert json_response(conn, 401)["errors"] == %{"auth" => "unauthenticated"}
end
end
describe "POST /v0/users" do
test "can create unprivileged user with password" do
params = %{
"email" => "new-user@test",
"role" => "unprivileged",
"password" => "test1234test",
"password_confirmation" => "test1234test"
}
conn =
post(authed_conn(), ~p"/v0/users", user: params)
|> doc()
assert json_response(conn, 201)["data"]["role"] == "unprivileged"
end
test "can create unprivileged user" do
params = %{"email" => "new-user@test", "role" => "unprivileged"}
conn = post(authed_conn(), ~p"/v0/users", user: params)
conn =
post(authed_conn(), ~p"/v0/users", user: params)
|> doc(example_description: "Provision an unprivileged OpenID User")
assert json_response(conn, 201)["data"]["role"] == "unprivileged"
end
test "can create admin user" do
params = %{"email" => "new-user@test", "role" => "admin"}
conn = post(authed_conn(), ~p"/v0/users", user: params)
conn =
post(authed_conn(), ~p"/v0/users", user: params)
|> doc(example_description: "Provision an admin OpenID User")
assert json_response(conn, 201)["data"]["role"] == "admin"
end
@@ -68,7 +94,9 @@ defmodule FzHttpWeb.JSON.UserControllerTest do
end
test "renders errors when data is invalid" do
conn = post(authed_conn(), ~p"/v0/users", user: @invalid_attrs)
conn =
post(authed_conn(), ~p"/v0/users", user: @invalid_attrs)
|> doc(example_description: "Error due to invalid parameters")
assert json_response(conn, 422)["errors"] == %{
"password" => [
@@ -84,16 +112,24 @@ defmodule FzHttpWeb.JSON.UserControllerTest do
end
end
describe "PUT /v0/users/:id" do
describe "PUT /v0/users/:id_or_email" do
test "returns user that was updated via email" do
user = user(%{role: :unprivileged})
conn = put(authed_conn(), ~p"/v0/users/#{user.email}", user: %{})
conn =
put(authed_conn(), ~p"/v0/users/#{user.email}", user: %{})
|> doc(example_description: "Update by email")
assert json_response(conn, 200)["data"]["id"] == user.id
end
test "returns user that was updated via id" do
user = user(%{role: :unprivileged})
conn = put(authed_conn(), ~p"/v0/users/#{user}", user: %{})
conn =
put(authed_conn(), ~p"/v0/users/#{user}", user: %{})
|> doc(example_description: "Update by ID")
assert json_response(conn, 200)["data"]["id"] == user.id
end
@@ -149,7 +185,9 @@ defmodule FzHttpWeb.JSON.UserControllerTest do
test "can update own role" do
conn = authed_conn()
user = conn.private.guardian_default_resource
conn = put(conn, ~p"/v0/users/#{user}", user: %{role: :unprivileged})
assert json_response(conn, 200)["data"]["role"] == "unprivileged"
end
@@ -174,9 +212,9 @@ defmodule FzHttpWeb.JSON.UserControllerTest do
end
test "renders 404 for user not found" do
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
put(authed_conn(), ~p"/v0/users/003da73d-2dd9-4492-8136-3282843545e8", user: %{})
end
end)
end
test "renders 401 for missing authorization header" do
@@ -189,21 +227,27 @@ defmodule FzHttpWeb.JSON.UserControllerTest do
test "gets user by id" do
conn = authed_conn()
user = conn.private.guardian_default_resource
conn = get(conn, ~p"/v0/users/#{user}")
assert json_response(conn, 200)["data"]["id"] == user.id
end
test "gets user by email" do
conn = authed_conn()
user = conn.private.guardian_default_resource
conn = get(conn, ~p"/v0/users/#{user.email}")
conn =
get(conn, ~p"/v0/users/#{user.email}")
|> doc(example_description: "An email can be used instead of ID.")
assert json_response(conn, 200)["data"]["id"] == user.id
end
test "renders 404 for user not found" do
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
get(authed_conn(), ~p"/v0/users/003da73d-2dd9-4492-8136-3282843545e8")
end
end)
end
test "renders 401 for missing authorization header" do
@@ -215,28 +259,36 @@ defmodule FzHttpWeb.JSON.UserControllerTest do
describe "DELETE /v0/users/:id" do
test "deletes user by id" do
user = user(%{role: :unprivileged})
conn = delete(authed_conn(), ~p"/v0/users/#{user}")
conn =
delete(authed_conn(), ~p"/v0/users/#{user}")
|> doc()
assert response(conn, 204)
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
get(conn, ~p"/v0/users/#{user}")
end
end)
end
test "deletes user by email" do
user = user(%{role: :unprivileged})
conn = delete(authed_conn(), ~p"/v0/users/#{user.email}")
conn =
delete(authed_conn(), ~p"/v0/users/#{user.email}")
|> doc(example_description: "An email can be used instead of ID.")
assert response(conn, 204)
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
get(conn, ~p"/v0/users/#{user}")
end
end)
end
test "renders 404 for user not found" do
assert_error_sent 404, fn ->
assert_error_sent(404, fn ->
delete(authed_conn(), ~p"/v0/users/003da73d-2dd9-4492-8136-3282843545e8")
end
end)
end
test "renders 401 for missing authorization header" do

View File

@@ -28,6 +28,7 @@ defmodule FzHttpWeb.ApiCase do
import Plug.Conn
import Phoenix.ConnTest
import FzHttp.TestHelpers
import Bureaucrat.Helpers
use FzHttpWeb, :verified_routes

View File

@@ -0,0 +1,223 @@
defmodule Firezone.DocusaurusWriter do
@keep_req_headers ["authorization"]
@keep_resp_headers ["content-type", "location"]
def write(conns, path) do
File.mkdir_p!(path)
routes = Phoenix.Router.routes(List.first(conns).private.phoenix_router)
conns
|> Enum.group_by(& &1.private.phoenix_controller)
|> Enum.map(fn {controller, conns} ->
{module_doc, module_assigns, function_docs} = fetch_module_docs!(controller)
title =
Keyword.get_lazy(module_assigns, :title, fn ->
controller
|> to_string()
|> String.split(".")
|> List.last()
|> String.replace_trailing("Controller", "")
end)
path = Path.join(path, "#{String.downcase(title)}.mdx")
file = File.open!(path, [:write, :utf8])
w!(file, "---")
w!(file, docusaurus_header(module_assigns))
w!(file, "---")
w!(file, "\n")
w!(file, module_doc)
w!(file, "## API Documentation")
conns
|> Enum.group_by(& &1.private.phoenix_action)
# We order actions nicely
|> Enum.sort_by(fn
{:index, _} -> 1
{:show, _} -> 3
{:create, _} -> 2
{:update, _} -> 4
{:delete, _} -> 5
{_other, _} -> 1000
end)
|> Enum.map(fn {action, conns} ->
{path, verb} = fetch_route!(routes, controller, action)
{function_doc, function_assigns} = get_function_docs(function_docs, action)
title = maybe_wrap(function_assigns[:action], "#{verb} #{path}")
w!(file, "### #{title}")
w!(file, "\n")
w!(file, function_doc)
uri_params = build_uri_params(path)
write_examples(file, conns, path, uri_params)
end)
end)
end
defp maybe_wrap(nil, title), do: title
defp maybe_wrap(action_assign, title), do: "#{action_assign} [`#{title}`]"
defp docusaurus_header(assigns) do
assigns
|> Enum.map_join("\n", fn {key, value} ->
"#{key}: #{value}"
end)
end
defp fetch_route!(routes, controller, controller_action) do
%{path: path, verb: verb} =
Enum.find(routes, fn
%{plug: ^controller, plug_opts: ^controller_action} -> true
_other -> false
end)
path = String.replace(path, ~r|:([^/]*)|, "{\\1}")
verb = verb |> to_string() |> String.upcase()
{path, verb}
end
defp fetch_module_docs!(controller) do
case Code.fetch_docs(controller) do
{:docs_v1, _, _, _, module_doc, %{api_doc: module_assigns}, function_docs} ->
{get_doc(module_doc), module_assigns, function_docs}
{:error, :module_not_found} ->
raise "No module #{controller}"
end
end
defp get_doc(md) when is_map(md), do: Map.get(md, "en")
defp get_doc(_md), do: nil
defp get_function_docs(function_docs, function) do
function_docs
|> Enum.find(fn
{{:function, ^function, _}, _, _, _, _} -> true
{{:function, _function, _}, _, _, _, _} -> false
end)
|> case do
{_, _, _, :none, %{api_doc: function_assigns}} ->
{nil, function_assigns}
{_, _, _, doc, %{api_doc: function_assigns}} ->
{get_doc(doc), function_assigns}
{_, _, _, doc, _chunks} ->
{get_doc(doc), %{}}
_other ->
{nil, %{}}
end
end
defp build_uri_params(path) do
Regex.scan(~r/{([^}]*)}/, path)
|> Enum.map(fn [_, param] ->
param
end)
end
defp write_examples(file, conns, path, uri_params) do
conns
|> Enum.sort_by(& &1.status)
|> Enum.each(fn conn ->
example_description = conn.assigns.bureaucrat_opts[:example_description] || "Example"
w!(file, "#### #{example_description}")
w_req_uri_params!(file, conn, uri_params)
w!(
file,
"""
```bash
$ curl -i \\
-X #{conn.method} "https://{firezone_host}#{path}" \\
-H 'Content-Type: application/json' \\
"""
|> String.trim_trailing()
)
maybe_w!(file, b_req_headers(conn))
maybe_w!(file, b_req_body(conn.body_params))
w!(file, "")
w!(file, "HTTP/1.1 #{conn.status}")
maybe_w!(file, b_resp_headers(conn))
maybe_w!(file, b_resp_body(conn.resp_body))
w!(file, "```")
end)
end
defp w_req_uri_params!(_file, _conn, []), do: :ok
defp w_req_uri_params!(file, conn, params) do
w!(file, "**URI Parameters:**\n")
Enum.each(params, fn param ->
w!(file, i(1, "- `#{param}`: `#{conn.params[param]}`"))
end)
end
defp b_req_headers(conn) do
for {key, value} <- conn.req_headers, key in @keep_req_headers do
case {key, value} do
{"authorization", "bearer " <> _} ->
i(1, "-H 'Authorization: Bearer {api_token}' \\")
{key, value} ->
i(1, "-H '#{camelize_header_key(key)}: #{value}' \\")
end
end
|> Enum.join("\n")
end
defp b_req_body(params) when params == %{}, do: ""
defp b_req_body(params) do
i(1, "--data-binary @- << EOF\n#{Jason.encode!(params, pretty: true)}'\nEOF")
end
defp b_resp_headers(conn) do
for {key, value} <- conn.resp_headers, key in @keep_resp_headers do
"#{camelize_header_key(key)}: #{value}"
end
|> Enum.join("\n")
end
defp b_resp_body(resp_body) do
case Jason.decode(resp_body) do
{:ok, map} ->
"\n" <> Jason.encode!(map, pretty: true)
_error ->
resp_body
end
end
defp camelize_header_key(key) do
key
|> String.split("-")
|> Enum.map_join("-", fn
<<first::utf8, rest::binary>> -> String.upcase(<<first::utf8>>) <> rest
other -> other
end)
end
defp i(level, text) do
String.duplicate(" ", level) <> text
end
defp maybe_w!(_file, ""), do: :ok
defp maybe_w!(_file, nil), do: :ok
defp maybe_w!(file, text), do: w!(file, text)
defp w!(file, content) do
IO.puts(file, content)
end
end

View File

@@ -1,4 +1,9 @@
Ecto.Adapters.SQL.Sandbox.mode(FzHttp.Repo, :manual)
Mox.defmock(OpenIDConnect.Mock, for: OpenIDConnect.MockBehaviour)
ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(FzHttp.Repo, :manual)
Bureaucrat.start(
writer: Firezone.DocusaurusWriter,
default_path: "../../docs/docs/reference/api"
)
ExUnit.start(formatters: [ExUnit.CLIFormatter, Bureaucrat.Formatter])

View File

@@ -64,3 +64,5 @@ config :fz_vpn,
wg_adapter: FzVpn.Interface.WGAdapter.Sandbox
config :argon2_elixir, t_cost: 1, m_cost: 8
config :bureaucrat, :json_library, Jason

View File

@@ -0,0 +1,4 @@
---
title: REST API
sidebar_position: 10
---

View File

@@ -0,0 +1,145 @@
---
title: Configurations
group: Configuration
---
This endpoint allows an administrator to manage Configurations.
## API Documentation
### GET /v0/configuration
#### Example
```bash
$ curl -i \
-X GET "https://{firezone_host}/v0/configuration" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": {
"allow_unprivileged_device_configuration": true,
"allow_unprivileged_device_management": true,
"default_client_allowed_ips": "0.0.0.0/0,::/0",
"default_client_dns": "1.1.1.1,1.0.0.1",
"default_client_endpoint": "localhost:51820",
"default_client_mtu": 1280,
"default_client_persistent_keepalive": 25,
"disable_vpn_on_oidc_error": false,
"id": "8f17e873-de8a-4264-8567-39e450870306",
"inserted_at": "2023-01-13T06:00:43.178729Z",
"local_auth_enabled": true,
"logo": null,
"openid_connect_providers": [],
"saml_identity_providers": [],
"updated_at": "2023-01-13T06:00:43.178729Z",
"vpn_session_duration": 0
}
}
```
### PATCH /v0/configuration
#### Example
```bash
$ curl -i \
-X PUT "https://{firezone_host}/v0/configuration" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"configuration": {
"allow_unprivileged_device_configuration": false,
"allow_unprivileged_device_management": false,
"default_client_allowed_ips": "1.1.1.1,2.2.2.2",
"default_client_dns": "1.1.1.1",
"default_client_endpoint": "new-endpoint",
"default_client_mtu": 1100,
"default_client_persistent_keepalive": 1,
"disable_vpn_on_oidc_error": true,
"local_auth_enabled": false,
"openid_connect_providers": [
{
"auto_create_users": false,
"client_id": "test-id",
"client_secret": "test-secret",
"discovery_document_uri": "https://accounts.google.com/.well-known/openid-configuration",
"id": "google",
"label": "google",
"redirect_uri": "https://invalid",
"response_type": "response-type",
"scope": "test-scope"
}
],
"saml_identity_providers": [
{
"auto_create_users": false,
"base_url": "https://saml",
"id": "okta",
"label": "okta",
"metadata": "<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" entityID=\"http://localhost:8080/realms/firezone\">\n <md:IDPSSODescriptor WantAuthnRequestsSigned=\"true\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:KeyDescriptor use=\"signing\">\n <ds:KeyInfo>\n <ds:KeyName>pdSMtx2s3RVVhxg_qJOjHhlZhwZk6JiBMiSm5PEgjkA</ds:KeyName>\n <ds:X509Data>\n <ds:X509Certificate>MIICnzCCAYcCBgGD18ZU8TANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhmaXJlem9uZTAeFw0yMjEwMTQxODMyMjJaFw0zMjEwMTQxODM0MDJaMBMxETAPBgNVBAMMCGZpcmV6b25lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAur5Cb0jrDJbMwr96WWE+z9CjDg0A/uRkaB4loRqkmu3A2fQGsS6CP7F7lQWMJmpzvBgkNtB69toO2sgx1u1fhpIJBZ0uSHF5gnzQAivgVxInvkMKRTRSkpMbhObiDHZnEGI2+Ly+8iV8IvprdrbDgm52u4conam0H1PewUKkHulrVQ+ImFuEWAjKCRSqpUG2F1eRkA0YpqB09x0CZAOOoucwTsBYj/ZAz3dUXhYIENAF7v0ykvzGOCAyOZIn1uYQc7jvWpwoI8qQdL45phj2FLoFlght3tlZV8IG5hsXrE6rg7Ufqvv8xyGltrOMKj/jEFEunagZOUjkypDp36b8cwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBEZKLLr66GB3NxqXGTMl0PvTDNB9GdyShQHaJYjeeUQnEXixjlAVrOq/txEBKjhGUcqyFELoNuwcxxV1iHA5oXhCoqYmnp9T/ftmXPDT3c49PBABHgLJaFOKYTpVx1YjP7mA44X1ijLZmgboIeeFNerVNHIzR9BsxcloQlB0r9QfC14rsuXo6QD3QnaVI8wDgWXQHqpcwLFqvehXdNvMFniRvX2qBNU8E0FPoMaZ1C3n2nssLcVZ+C4ghq6YoAG+wLGY7XE8+v5rnYGDpGpfgr2wdefn6tryFq3PyGqA8ThjARESRRQG9kI/RlNX7qCnP/8/7JQ4wLdfz5C25uhakP</ds:X509Certificate>\n </ds:X509Data>\n </ds:KeyInfo>\n </md:KeyDescriptor>\n <md:ArtifactResolutionService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://localhost:8080/realms/firezone/protocol/saml/resolve\" index=\"0\"/>\n <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>\n <md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n </md:IDPSSODescriptor>\n</md:EntityDescriptor>\n",
"sign_metadata": false,
"sign_requests": false,
"signed_assertion_in_resp": false,
"signed_envelopes_in_resp": false
}
],
"vpn_session_duration": 100
}
}'
EOF
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": {
"allow_unprivileged_device_configuration": false,
"allow_unprivileged_device_management": false,
"default_client_allowed_ips": "1.1.1.1,2.2.2.2",
"default_client_dns": "1.1.1.1",
"default_client_endpoint": "new-endpoint",
"default_client_mtu": 1100,
"default_client_persistent_keepalive": 1,
"disable_vpn_on_oidc_error": true,
"id": "8f17e873-de8a-4264-8567-39e450870306",
"inserted_at": "2023-01-13T06:00:43.178729Z",
"local_auth_enabled": false,
"logo": null,
"openid_connect_providers": [
{
"auto_create_users": false,
"client_id": "test-id",
"client_secret": "test-secret",
"discovery_document_uri": "https://accounts.google.com/.well-known/openid-configuration",
"id": "google",
"label": "google",
"redirect_uri": "https://invalid",
"response_type": "response-type",
"scope": "test-scope"
}
],
"saml_identity_providers": [
{
"auto_create_users": false,
"base_url": "https://saml",
"id": "okta",
"label": "okta",
"metadata": "<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" entityID=\"http://localhost:8080/realms/firezone\">\n <md:IDPSSODescriptor WantAuthnRequestsSigned=\"true\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:KeyDescriptor use=\"signing\">\n <ds:KeyInfo>\n <ds:KeyName>pdSMtx2s3RVVhxg_qJOjHhlZhwZk6JiBMiSm5PEgjkA</ds:KeyName>\n <ds:X509Data>\n <ds:X509Certificate>MIICnzCCAYcCBgGD18ZU8TANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhmaXJlem9uZTAeFw0yMjEwMTQxODMyMjJaFw0zMjEwMTQxODM0MDJaMBMxETAPBgNVBAMMCGZpcmV6b25lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAur5Cb0jrDJbMwr96WWE+z9CjDg0A/uRkaB4loRqkmu3A2fQGsS6CP7F7lQWMJmpzvBgkNtB69toO2sgx1u1fhpIJBZ0uSHF5gnzQAivgVxInvkMKRTRSkpMbhObiDHZnEGI2+Ly+8iV8IvprdrbDgm52u4conam0H1PewUKkHulrVQ+ImFuEWAjKCRSqpUG2F1eRkA0YpqB09x0CZAOOoucwTsBYj/ZAz3dUXhYIENAF7v0ykvzGOCAyOZIn1uYQc7jvWpwoI8qQdL45phj2FLoFlght3tlZV8IG5hsXrE6rg7Ufqvv8xyGltrOMKj/jEFEunagZOUjkypDp36b8cwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBEZKLLr66GB3NxqXGTMl0PvTDNB9GdyShQHaJYjeeUQnEXixjlAVrOq/txEBKjhGUcqyFELoNuwcxxV1iHA5oXhCoqYmnp9T/ftmXPDT3c49PBABHgLJaFOKYTpVx1YjP7mA44X1ijLZmgboIeeFNerVNHIzR9BsxcloQlB0r9QfC14rsuXo6QD3QnaVI8wDgWXQHqpcwLFqvehXdNvMFniRvX2qBNU8E0FPoMaZ1C3n2nssLcVZ+C4ghq6YoAG+wLGY7XE8+v5rnYGDpGpfgr2wdefn6tryFq3PyGqA8ThjARESRRQG9kI/RlNX7qCnP/8/7JQ4wLdfz5C25uhakP</ds:X509Certificate>\n </ds:X509Data>\n </ds:KeyInfo>\n </md:KeyDescriptor>\n <md:ArtifactResolutionService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://localhost:8080/realms/firezone/protocol/saml/resolve\" index=\"0\"/>\n <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>\n <md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n <md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\" Location=\"http://localhost:8080/realms/firezone/protocol/saml\"/>\n </md:IDPSSODescriptor>\n</md:EntityDescriptor>\n",
"sign_metadata": false,
"sign_requests": false,
"signed_assertion_in_resp": false,
"signed_envelopes_in_resp": false
}
],
"updated_at": "2023-01-13T06:30:47.529652Z",
"vpn_session_duration": 100
}
}
```

View File

@@ -0,0 +1,363 @@
---
title: Devices
group: Devices
---
This endpoint allows an administrator to manage Devices.
## API Documentation
### GET /v0/devices
#### Example
```bash
$ curl -i \
-X GET "https://{firezone_host}/v0/devices" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": [
{
"allowed_ips": "0.0.0.0/0,::/0",
"description": "factory description",
"dns": "1.1.1.1,1.0.0.1",
"endpoint": "localhost:51820",
"id": "9fc9b189-b740-4676-a7c2-c135b97d951d",
"inserted_at": "2023-01-13T06:30:47.092141Z",
"ipv4": "100.99.95.137",
"ipv6": "fd00::2a:3f6d",
"latest_handshake": null,
"mtu": 1280,
"name": "factory 3175",
"persistent_keepalive": 25,
"preshared_key": "Fj2iQKy5R5lLi8GIMI96FIfgWs/8saEYj4q6mLtgpg8=",
"public_key": "cVAdAno+PbyPBg5ubwxPe86QahSZ3AwhsKHfkFNw0nc=",
"remote_ip": null,
"rx_bytes": null,
"server_public_key": "is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=",
"tx_bytes": null,
"updated_at": "2023-01-13T06:30:47.092141Z",
"use_default_allowed_ips": true,
"use_default_dns": true,
"use_default_endpoint": true,
"use_default_mtu": true,
"use_default_persistent_keepalive": true,
"user_id": "4b195eb9-d675-4143-8de0-64e68c5a6d86"
},
{
"allowed_ips": "0.0.0.0/0,::/0",
"description": "factory description",
"dns": "1.1.1.1,1.0.0.1",
"endpoint": "localhost:51820",
"id": "85243eb4-8c30-4bce-bb40-b4b9650e7ffa",
"inserted_at": "2023-01-13T06:30:47.101527Z",
"ipv4": "100.106.156.151",
"ipv6": "fd00::3e:876d",
"latest_handshake": null,
"mtu": 1280,
"name": "factory 2820",
"persistent_keepalive": 25,
"preshared_key": "NXiX1/xSyDfEl+S3O7VaaVTJKUu2kZo91pCycuZG3mk=",
"public_key": "e8e8+NBCAyHPycu2VJIRK9NQCR5Bz5Oo6aFbuOmUMhc=",
"remote_ip": null,
"rx_bytes": null,
"server_public_key": "is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=",
"tx_bytes": null,
"updated_at": "2023-01-13T06:30:47.101527Z",
"use_default_allowed_ips": true,
"use_default_dns": true,
"use_default_endpoint": true,
"use_default_mtu": true,
"use_default_persistent_keepalive": true,
"user_id": "1949803a-bb74-429c-ac99-fae4ce09ca08"
},
{
"allowed_ips": "0.0.0.0/0,::/0",
"description": "factory description",
"dns": "1.1.1.1,1.0.0.1",
"endpoint": "localhost:51820",
"id": "7898b569-7c59-4596-b002-e1717c6fe5df",
"inserted_at": "2023-01-13T06:30:47.108829Z",
"ipv4": "100.120.62.86",
"ipv6": "fd00::38:7ee5",
"latest_handshake": null,
"mtu": 1280,
"name": "factory 2978",
"persistent_keepalive": 25,
"preshared_key": "i6kyqjysbWGEWMO9FNUDMxE1OhrYJsgjuIfgnJNApyE=",
"public_key": "ulryM87WfDob8foWZtIiaW+cH+ugh4t/31vSO2YNRtA=",
"remote_ip": null,
"rx_bytes": null,
"server_public_key": "is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=",
"tx_bytes": null,
"updated_at": "2023-01-13T06:30:47.108829Z",
"use_default_allowed_ips": true,
"use_default_dns": true,
"use_default_endpoint": true,
"use_default_mtu": true,
"use_default_persistent_keepalive": true,
"user_id": "a1ebe3b2-3843-47ee-baf4-077740bfcd35"
},
{
"allowed_ips": "0.0.0.0/0,::/0",
"description": "factory description",
"dns": "1.1.1.1,1.0.0.1",
"endpoint": "localhost:51820",
"id": "3113df9e-29a4-4062-8a7d-7bf67612c61c",
"inserted_at": "2023-01-13T06:30:47.115812Z",
"ipv4": "100.64.196.12",
"ipv6": "fd00::1e:b441",
"latest_handshake": null,
"mtu": 1280,
"name": "factory 1577",
"persistent_keepalive": 25,
"preshared_key": "oZsi27RP/myoPFDTdxv29KVAxb2D1PTN+ojhUHsuM9I=",
"public_key": "ZZrmY3JKOLJc9JZO+JTBJ9toM+x3hZBikcEAAxhdnWY=",
"remote_ip": null,
"rx_bytes": null,
"server_public_key": "is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=",
"tx_bytes": null,
"updated_at": "2023-01-13T06:30:47.115812Z",
"use_default_allowed_ips": true,
"use_default_dns": true,
"use_default_endpoint": true,
"use_default_mtu": true,
"use_default_persistent_keepalive": true,
"user_id": "634c7a8b-95c3-4c2f-9566-78be4d768cde"
},
{
"allowed_ips": "0.0.0.0/0,::/0",
"description": "factory description",
"dns": "1.1.1.1,1.0.0.1",
"endpoint": "localhost:51820",
"id": "44c8dd33-ad63-47cf-93ae-ff5ffcb16f52",
"inserted_at": "2023-01-13T06:30:47.123039Z",
"ipv4": "100.93.227.124",
"ipv6": "fd00::3:b1dd",
"latest_handshake": null,
"mtu": 1280,
"name": "factory 1673",
"persistent_keepalive": 25,
"preshared_key": "G0XAE7HCOArcB+RH09g/HIsDJM3SfYk9WKa1WYo6hv4=",
"public_key": "RBr9GYzFGuoltoIt2iTeqSb0CHaiPAcwxxxxSYlX4tg=",
"remote_ip": null,
"rx_bytes": null,
"server_public_key": "is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=",
"tx_bytes": null,
"updated_at": "2023-01-13T06:30:47.123039Z",
"use_default_allowed_ips": true,
"use_default_dns": true,
"use_default_endpoint": true,
"use_default_mtu": true,
"use_default_persistent_keepalive": true,
"user_id": "b61a4d7a-34a6-4d37-9871-a024fb97fe60"
}
]
}
```
### POST /v0/devices
#### Example
```bash
$ curl -i \
-X POST "https://{firezone_host}/v0/devices" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"device": {
"allowed_ips": "0.0.0.0/0, ::/0, 1.1.1.1",
"description": "create-description",
"dns": "9.9.9.8",
"endpoint": "9.9.9.9",
"ipv4": "100.64.0.2",
"ipv6": "fd00::2",
"mtu": 999,
"name": "create-name",
"persistent_keepalive": 9,
"preshared_key": "CHqFuS+iL3FTog5F4Ceumqlk0CU4Cl/dyUP/9F9NDnI=",
"public_key": "CHqFuS+iL3FTog5F4Ceumqlk0CU4Cl/dyUP/9F9NDnI=",
"use_default_allowed_ips": false,
"use_default_dns": false,
"use_default_endpoint": false,
"use_default_mtu": false,
"use_default_persistent_keepalive": false,
"user_id": "57afecab-c92c-45b8-8764-017a7cb5276b"
}
}'
EOF
HTTP/1.1 201
Content-Type: application/json; charset=utf-8
Location: /v0/devices/0f71030e-b872-494f-af2b-c31730f119e0
{
"data": {
"allowed_ips": "0.0.0.0/0, ::/0, 1.1.1.1",
"description": "create-description",
"dns": "9.9.9.8",
"endpoint": "9.9.9.9",
"id": "0f71030e-b872-494f-af2b-c31730f119e0",
"inserted_at": "2023-01-13T06:30:47.204640Z",
"ipv4": "100.64.0.2",
"ipv6": "fd00::2",
"latest_handshake": null,
"mtu": 999,
"name": "create-name",
"persistent_keepalive": 9,
"preshared_key": "CHqFuS+iL3FTog5F4Ceumqlk0CU4Cl/dyUP/9F9NDnI=",
"public_key": "CHqFuS+iL3FTog5F4Ceumqlk0CU4Cl/dyUP/9F9NDnI=",
"remote_ip": null,
"rx_bytes": null,
"server_public_key": "is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=",
"tx_bytes": null,
"updated_at": "2023-01-13T06:30:47.204640Z",
"use_default_allowed_ips": false,
"use_default_dns": false,
"use_default_endpoint": false,
"use_default_mtu": false,
"use_default_persistent_keepalive": false,
"user_id": "57afecab-c92c-45b8-8764-017a7cb5276b"
}
}
```
### GET /v0/devices/{id}
#### Example
**URI Parameters:**
- `id`: `904fbe05-86a5-4edb-afb2-6b728755a210`
```bash
$ curl -i \
-X GET "https://{firezone_host}/v0/devices/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": {
"allowed_ips": "0.0.0.0/0,::/0",
"description": "factory description",
"dns": "1.1.1.1,1.0.0.1",
"endpoint": "localhost:51820",
"id": "904fbe05-86a5-4edb-afb2-6b728755a210",
"inserted_at": "2023-01-13T06:30:47.048145Z",
"ipv4": "100.74.213.26",
"ipv6": "fd00::5:110a",
"latest_handshake": null,
"mtu": 1280,
"name": "factory 2403",
"persistent_keepalive": 25,
"preshared_key": "fue0i7cIahPPlWr6UCSerJNu3NRDrETv3YW+Xjc3qU4=",
"public_key": "6KYOCkP8off66kidQOXpwQlwKix8ELfXm/kR5cwsnug=",
"remote_ip": null,
"rx_bytes": null,
"server_public_key": "is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=",
"tx_bytes": null,
"updated_at": "2023-01-13T06:30:47.048145Z",
"use_default_allowed_ips": true,
"use_default_dns": true,
"use_default_endpoint": true,
"use_default_mtu": true,
"use_default_persistent_keepalive": true,
"user_id": "bc22e5c4-853c-4e0d-bf93-85111707e66f"
}
}
```
### PATCH /v0/devices/{id}
#### Example
**URI Parameters:**
- `id`: `ef1da923-caf4-47f3-ab6a-7ab0908d7f0e`
```bash
$ curl -i \
-X PUT "https://{firezone_host}/v0/devices/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"device": {
"allowed_ips": "0.0.0.0/0, ::/0, 1.1.1.1",
"description": "create-description",
"dns": "9.9.9.8",
"endpoint": "9.9.9.9",
"ipv4": "100.64.0.2",
"ipv6": "fd00::2",
"mtu": 999,
"name": "create-name",
"persistent_keepalive": 9,
"preshared_key": "CHqFuS+iL3FTog5F4Ceumqlk0CU4Cl/dyUP/9F9NDnI=",
"public_key": "CHqFuS+iL3FTog5F4Ceumqlk0CU4Cl/dyUP/9F9NDnI=",
"use_default_allowed_ips": false,
"use_default_dns": false,
"use_default_endpoint": false,
"use_default_mtu": false,
"use_default_persistent_keepalive": false
}
}'
EOF
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": {
"allowed_ips": "0.0.0.0/0, ::/0, 1.1.1.1",
"description": "create-description",
"dns": "9.9.9.8",
"endpoint": "9.9.9.9",
"id": "ef1da923-caf4-47f3-ab6a-7ab0908d7f0e",
"inserted_at": "2023-01-13T06:30:47.264719Z",
"ipv4": "100.64.0.2",
"ipv6": "fd00::2",
"latest_handshake": null,
"mtu": 999,
"name": "create-name",
"persistent_keepalive": 9,
"preshared_key": "CHqFuS+iL3FTog5F4Ceumqlk0CU4Cl/dyUP/9F9NDnI=",
"public_key": "CHqFuS+iL3FTog5F4Ceumqlk0CU4Cl/dyUP/9F9NDnI=",
"remote_ip": null,
"rx_bytes": null,
"server_public_key": "is+0ov0/SZ9I+qyDD+adVoH9LreWHa85QQgpt6RUtA4=",
"tx_bytes": null,
"updated_at": "2023-01-13T06:30:47.279141Z",
"use_default_allowed_ips": false,
"use_default_dns": false,
"use_default_endpoint": false,
"use_default_mtu": false,
"use_default_persistent_keepalive": false,
"user_id": "ae05e328-3a67-4101-9d6b-48dcd9cdddf8"
}
}
```
### DELETE /v0/devices/{id}
#### Example
**URI Parameters:**
- `id`: `5b8cf677-ae60-4eca-b038-9abcd410904f`
```bash
$ curl -i \
-X DELETE "https://{firezone_host}/v0/devices/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 204
```

View File

@@ -0,0 +1,203 @@
---
title: Rules
group: Rules
---
This endpoint allows an adminisrator to manage Rules.
## API Documentation
### GET /v0/rules
#### Example
```bash
$ curl -i \
-X GET "https://{firezone_host}/v0/rules" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": [
{
"action": "drop",
"destination": "10.3.2.1",
"id": "fdcf2ca8-1871-4aac-bc01-591b3e18578e",
"inserted_at": "2023-01-13T06:30:46.079209Z",
"port_range": null,
"port_type": null,
"updated_at": "2023-01-13T06:30:46.079209Z",
"user_id": null
},
{
"action": "drop",
"destination": "10.3.2.2",
"id": "2e544ab0-0ca5-432e-9a31-b5feb982f50a",
"inserted_at": "2023-01-13T06:30:46.087033Z",
"port_range": null,
"port_type": null,
"updated_at": "2023-01-13T06:30:46.087033Z",
"user_id": null
},
{
"action": "drop",
"destination": "10.3.2.3",
"id": "02df6455-65f1-406b-a3dd-7c223af04f9b",
"inserted_at": "2023-01-13T06:30:46.088443Z",
"port_range": null,
"port_type": null,
"updated_at": "2023-01-13T06:30:46.088443Z",
"user_id": null
},
{
"action": "drop",
"destination": "10.3.2.4",
"id": "e0c4e652-e3a2-4a69-a8f3-265e07515938",
"inserted_at": "2023-01-13T06:30:46.090111Z",
"port_range": null,
"port_type": null,
"updated_at": "2023-01-13T06:30:46.090111Z",
"user_id": null
},
{
"action": "drop",
"destination": "10.3.2.5",
"id": "a7c2bfb2-3e09-48d8-b038-c69c84b777b1",
"inserted_at": "2023-01-13T06:30:46.091623Z",
"port_range": null,
"port_type": null,
"updated_at": "2023-01-13T06:30:46.091623Z",
"user_id": null
}
]
}
```
### POST /v0/rules
#### Example
```bash
$ curl -i \
-X POST "https://{firezone_host}/v0/rules" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"rule": {
"action": "accept",
"destination": "1.1.1.1/24",
"port_range": "1 - 2",
"port_type": "udp",
"user_id": "9f4d207a-90d8-4cc9-800c-accefe9f90cf"
}
}'
EOF
HTTP/1.1 201
Content-Type: application/json; charset=utf-8
Location: /v0/rules/cc374f84-b003-4858-8772-516ea3f098a1
{
"data": {
"action": "accept",
"destination": "1.1.1.1/24",
"id": "cc374f84-b003-4858-8772-516ea3f098a1",
"inserted_at": "2023-01-13T06:30:47.193190Z",
"port_range": "1 - 2",
"port_type": "udp",
"updated_at": "2023-01-13T06:30:47.193190Z",
"user_id": "9f4d207a-90d8-4cc9-800c-accefe9f90cf"
}
}
```
### GET /v0/rules/{id}
#### Example
**URI Parameters:**
- `id`: `49a9ae27-74f2-45dd-a324-d47a7581205c`
```bash
$ curl -i \
-X GET "https://{firezone_host}/v0/rules/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": {
"action": "drop",
"destination": "10.10.10.0/24",
"id": "49a9ae27-74f2-45dd-a324-d47a7581205c",
"inserted_at": "2023-01-13T06:30:46.993421Z",
"port_range": null,
"port_type": null,
"updated_at": "2023-01-13T06:30:46.993421Z",
"user_id": null
}
}
```
### PATCH /v0/rules/{id}
#### Example
**URI Parameters:**
- `id`: `b8231fa6-1df2-4b1b-8687-61a72a8031b1`
```bash
$ curl -i \
-X PUT "https://{firezone_host}/v0/rules/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"rule": {
"action": "accept",
"destination": "1.1.1.1/24",
"port_range": "1 - 2",
"port_type": "udp"
}
}'
EOF
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": {
"action": "accept",
"destination": "1.1.1.1/24",
"id": "b8231fa6-1df2-4b1b-8687-61a72a8031b1",
"inserted_at": "2023-01-13T06:30:47.244050Z",
"port_range": "1 - 2",
"port_type": "udp",
"updated_at": "2023-01-13T06:30:47.254788Z",
"user_id": null
}
}
```
### DELETE /v0/rules/{id}
#### Example
**URI Parameters:**
- `id`: `05c0342c-984c-43df-855b-32e88ea8ee08`
```bash
$ curl -i \
-X DELETE "https://{firezone_host}/v0/rules/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 204
```

View File

@@ -0,0 +1,354 @@
---
title: Users
sidebar_position: 2
toc_max_heading_level: 4
---
This endpoint allows an administrator to manage Users.
## Auto-Create Users from OpenID or SAML providers
You can set Configuration option `auto_create_users` to `true` to automatically create users
from OpenID or SAML providers. Use it with care as anyone with access to the provider will be
able to log-in to Firezone.
If `auto_create_users` is `false`, then you need to provision users with `password` attribute,
otherwise they will have no means to log in.
## API Documentation
### List all Users [`GET /v0/users`]
#### Example
```bash
$ curl -i \
-X GET "https://{firezone_host}/v0/users" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": [
{
"disabled_at": null,
"email": "test-2886@test",
"id": "9f0ce70d-d9e6-4610-ad3b-e5758318c016",
"inserted_at": "2023-01-13T06:30:47.076850Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "admin",
"updated_at": "2023-01-13T06:30:47.076850Z"
},
{
"disabled_at": null,
"email": "test-2918@test",
"id": "36479416-7099-46f9-b9b9-3ad4411eef7d",
"inserted_at": "2023-01-13T06:30:47.079115Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "admin",
"updated_at": "2023-01-13T06:30:47.079115Z"
},
{
"disabled_at": null,
"email": "test-3045@test",
"id": "232c2358-5132-4fc7-8e42-cd8464fcae02",
"inserted_at": "2023-01-13T06:30:47.081138Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "admin",
"updated_at": "2023-01-13T06:30:47.081138Z"
},
{
"disabled_at": null,
"email": "test-2950@test",
"id": "b15b274b-751e-4ca6-9c3e-3a798299ec86",
"inserted_at": "2023-01-13T06:30:47.083059Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "admin",
"updated_at": "2023-01-13T06:30:47.083059Z"
}
]
}
```
### Create a User [`POST /v0/users`]
Create a new User.
This endpoint is useful in two cases:
1. When [Local Authentication](/authenticate/local-auth/) is enabled (discouraged in
production deployments), it allows an administrator to provision users with their passwords;
2. When `auto_create_users` in the associated OpenID or SAML configuration is disabled,
it allows an administrator to provision users with their emails beforehand, effectively
whitelisting specific users for authentication.
If `auto_create_users` is `true` in the associated OpenID or SAML configuration, there is no need
to provision users; they will be created automatically when they log in for the first time using
the associated OpenID or SAML provider.
#### User Attributes
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `role` | `admin` or `unprivileged` (default) | No | User role. |
| `email` | `string` | Yes | Email which will be used to identify the user. |
| `password` | `string` | No | A password that can be used for login-password authentication. |
| `password_confirmation` | `string` | -> | Is required when the `password` is set. |
#### Example
```bash
$ curl -i \
-X POST "https://{firezone_host}/v0/users" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"user": {
"email": "new-user@test",
"password": "test1234test",
"password_confirmation": "test1234test",
"role": "unprivileged"
}
}'
EOF
HTTP/1.1 201
Content-Type: application/json; charset=utf-8
Location: /v0/users/86616e3e-13f0-4177-bc8e-1a0e588f0be8
{
"data": {
"disabled_at": null,
"email": "new-user@test",
"id": "86616e3e-13f0-4177-bc8e-1a0e588f0be8",
"inserted_at": "2023-01-13T06:30:47.047550Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "unprivileged",
"updated_at": "2023-01-13T06:30:47.047550Z"
}
}
```
#### Provision an unprivileged OpenID User
```bash
$ curl -i \
-X POST "https://{firezone_host}/v0/users" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"user": {
"email": "new-user@test",
"role": "unprivileged"
}
}'
EOF
HTTP/1.1 201
Content-Type: application/json; charset=utf-8
Location: /v0/users/6e7962a7-c183-4afb-8569-9001bdfd0d87
{
"data": {
"disabled_at": null,
"email": "new-user@test",
"id": "6e7962a7-c183-4afb-8569-9001bdfd0d87",
"inserted_at": "2023-01-13T06:30:47.282412Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "unprivileged",
"updated_at": "2023-01-13T06:30:47.282412Z"
}
}
```
#### Provision an admin OpenID User
```bash
$ curl -i \
-X POST "https://{firezone_host}/v0/users" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"user": {
"email": "new-user@test",
"role": "admin"
}
}'
EOF
HTTP/1.1 201
Content-Type: application/json; charset=utf-8
Location: /v0/users/dedc4dcc-0f65-4110-ad7f-9c354e36e5e5
{
"data": {
"disabled_at": null,
"email": "new-user@test",
"id": "dedc4dcc-0f65-4110-ad7f-9c354e36e5e5",
"inserted_at": "2023-01-13T06:30:47.166645Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "admin",
"updated_at": "2023-01-13T06:30:47.166645Z"
}
}
```
#### Error due to invalid parameters
```bash
$ curl -i \
-X POST "https://{firezone_host}/v0/users" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"user": {
"email": "test@test.com",
"password": "test1234"
}
}'
EOF
HTTP/1.1 422
Content-Type: application/json; charset=utf-8
{
"errors": {
"password": [
"should be at least 12 character(s)",
"does not match password confirmation."
]
}
}
```
### GET /v0/users/{id}
#### An email can be used instead of ID.
**URI Parameters:**
- `id`: `test-1481@test`
```bash
$ curl -i \
-X GET "https://{firezone_host}/v0/users/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": {
"disabled_at": null,
"email": "test-1481@test",
"id": "b19a929d-fd84-4f11-a799-23416c8efaf8",
"inserted_at": "2023-01-13T06:30:47.050304Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "admin",
"updated_at": "2023-01-13T06:30:47.050304Z"
}
}
```
### Update a User [`PATCH /v0/users/{id}`]
For details please see [Create a User](#create-a-user-post-v0users) section.
#### Update by email
**URI Parameters:**
- `id`: `test-3618@test`
```bash
$ curl -i \
-X PUT "https://{firezone_host}/v0/users/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"user": {}
}'
EOF
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": {
"disabled_at": null,
"email": "test-3618@test",
"id": "54d4de57-21f3-4adc-a9a9-a3ee642da76e",
"inserted_at": "2023-01-13T06:30:47.285730Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "unprivileged",
"updated_at": "2023-01-13T06:30:47.285730Z"
}
}
```
#### Update by ID
**URI Parameters:**
- `id`: `8dd4eff5-3d2f-4868-94cd-73abb6f130dc`
```bash
$ curl -i \
-X PUT "https://{firezone_host}/v0/users/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
--data-binary @- << EOF
{
"user": {}
}'
EOF
HTTP/1.1 200
Content-Type: application/json; charset=utf-8
{
"data": {
"disabled_at": null,
"email": "test-3235@test",
"id": "8dd4eff5-3d2f-4868-94cd-73abb6f130dc",
"inserted_at": "2023-01-13T06:30:47.265280Z",
"last_signed_in_at": null,
"last_signed_in_method": null,
"role": "unprivileged",
"updated_at": "2023-01-13T06:30:47.265280Z"
}
}
```
### DELETE /v0/users/{id}
#### Example
**URI Parameters:**
- `id`: `fc0b513f-bd4b-4015-ac71-29b59c678a20`
```bash
$ curl -i \
-X DELETE "https://{firezone_host}/v0/users/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 204
```
#### An email can be used instead of ID.
**URI Parameters:**
- `id`: `test-3816@test`
```bash
$ curl -i \
-X DELETE "https://{firezone_host}/v0/users/{id}" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {api_token}' \
HTTP/1.1 204
```

View File

@@ -43,28 +43,9 @@ const config = {
}
],
plugins: [
[
'docusaurus-plugin-openapi-docs',
{
id: "apiDocs",
docsPluginId: "classic",
config: {
rest_api: {
specPath: "openapi.yaml",
outputDir: "docs/reference/REST API",
sidebarOptions: {
groupPathsBy: "tag",
},
},
},
},
],
],
plugins: [],
themes: [
"docusaurus-theme-openapi-docs",
],
themes: [],
presets: [
[
@@ -75,8 +56,7 @@ const config = {
routeBasePath: '/',
sidebarPath: require.resolve('./sidebars.js'),
editUrl: 'https://github.com/firezone/firezone/tree/master/docs',
docLayoutComponent: "@theme/DocPage",
docItemComponent: "@theme/ApiItem",
docLayoutComponent: "@theme/DocPage"
},
theme: {
customCss: require.resolve('./src/css/custom.css'),

View File

@@ -1,111 +0,0 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
format: int32
responses:
'200':
description: A paged array of pets
headers:
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
'201':
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string

View File

@@ -19,8 +19,6 @@
"@docusaurus/theme-search-algolia": "^2.1.0",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"docusaurus-plugin-openapi-docs": "^1.2.2",
"docusaurus-theme-openapi-docs": "^1.2.2",
"markdownlint-cli": "^0.31.1",
"prism-react-renderer": "^1.3.3",
"react": "^17.0.2",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
%{
"argon2_elixir": {:hex, :argon2_elixir, "2.4.1", "edb27bdd326bc738f3e4614eddc2f73507be6fedc9533c6bcc6f15bbac9c85cc", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0e21f52a373739d00bdfd5fe6da2f04eea623cb4f66899f7526dd9db03903d9f"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"bureaucrat": {:hex, :bureaucrat, "0.2.9", "d98e4d2b9bdbf22e4a45c2113ce8b38b5b63278506c6ff918e3b943a4355d85b", [:mix], [{:inflex, ">= 1.10.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "111c8dd84382a62e1026ae011d592ceee918553e5203fe8448d9ba6ccbdfff7d"},
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
"castore": {:hex, :castore, "0.1.20", "62a0126cbb7cb3e259257827b9190f88316eb7aa3fdac01fd6f2dfd64e7f46e9", [:mix], [], "hexpm", "a020b7650529c986c454a4035b6b13a328e288466986307bea3aadb4c95ac98a"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},