From 07306000a6fba1762a00e054724de5adca6e2ae5 Mon Sep 17 00:00:00 2001 From: Luiz Carlos Date: Sat, 28 Nov 2020 18:16:46 -0300 Subject: [PATCH] feat: add initial swagger documentation --- .github/workflows/main.yml | 4 +- apps/rest_api/lib/controllers/public/auth.ex | 1 + apps/rest_api/lib/endpoint.ex | 2 +- apps/rest_api/lib/routers/default.ex | 77 +++++++++++++ apps/rest_api/lib/routers/public.ex | 42 ------- apps/rest_api/lib/swagger/auth_operations.ex | 112 +++++++++++++++++++ apps/rest_api/mix.exs | 6 +- config/config.exs | 19 +++- mix.exs | 4 +- mix.lock | 2 + 10 files changed, 216 insertions(+), 53 deletions(-) create mode 100644 apps/rest_api/lib/routers/default.ex delete mode 100644 apps/rest_api/lib/routers/public.ex create mode 100644 apps/rest_api/lib/swagger/auth_operations.ex diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cfcb741..90c2449 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -109,13 +109,13 @@ jobs: uses: actions/cache@v2 id: plt-cache with: - path: dializer/ + path: dializer/plts/ key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plts-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} - name: Generate PLT if: steps.plt-cache.outputs.cache-hit != 'true' run: | - mkdir -p dialyzer/ + mkdir -p dialyzer/plts/ mix dialyzer --plt - name: Run dialyzer diff --git a/apps/rest_api/lib/controllers/public/auth.ex b/apps/rest_api/lib/controllers/public/auth.ex index 72870d0..1100eb3 100644 --- a/apps/rest_api/lib/controllers/public/auth.ex +++ b/apps/rest_api/lib/controllers/public/auth.ex @@ -2,6 +2,7 @@ defmodule RestAPI.Controllers.Public.Auth do @moduledoc false use RestAPI.Controller, :controller + use RestAPI.Swagger.AuthOperations alias RestAPI.Ports.Authenticator, as: Commands alias RestAPI.Views.Public.SignIn diff --git a/apps/rest_api/lib/endpoint.ex b/apps/rest_api/lib/endpoint.ex index 3f8e2f2..7789455 100644 --- a/apps/rest_api/lib/endpoint.ex +++ b/apps/rest_api/lib/endpoint.ex @@ -19,5 +19,5 @@ defmodule RestAPI.Endpoint do plug Plug.Head # Routers - plug RestAPI.Routers.Public + plug RestAPI.Routers.Default end diff --git a/apps/rest_api/lib/routers/default.ex b/apps/rest_api/lib/routers/default.ex new file mode 100644 index 0000000..9772f3f --- /dev/null +++ b/apps/rest_api/lib/routers/default.ex @@ -0,0 +1,77 @@ +defmodule RestAPI.Routers.Default do + @moduledoc false + + use RestAPI.Router + + alias PhoenixSwagger.Plug.SwaggerUI + + alias RestAPI.Controllers.Public + alias RestAPI.Plugs.{Authentication, Authorization} + + pipeline :rest_api do + plug :accepts, ["json"] + end + + pipeline :authenticated do + plug Authentication + end + + pipeline :authorized_by_admin do + plug Authorization, type: "admin" + end + + # This should be used only for documentation purposes + # When running in production it should be disabled + scope "/api/swagger" do + forward "/", SwaggerUI, otp_app: :rest_api, swagger_file: "swagger.json" + end + + scope "/api/v1", Public do + pipe_through :rest_api + + scope "/auth/protocol/openid-connect" do + post "/token", Auth, :sign_in + + scope "/" do + pipe_through :authenticated + + post "/logout", Auth, :sign_out + post "/logout-all-sessions", Auth, :sign_out_all_sessions + end + end + end + + scope "/admin/v1", RestAPI.Controller.Admin do + pipe_through :authenticated + pipe_through :authorized_by_admin + + resources("/users", User, except: [:new]) + end + + def swagger_info do + %{ + schemes: ["https", "http"], + info: %{ + version: "1.0", + title: "WatcherEx", + description: "An Oauth2 and Resource server interelly in elixir.", + termsOfService: "Open for public", + contact: %{ + name: "Luiz Carlos", + email: "lcpojr@gmail.com" + } + }, + securityDefinitions: %{ + Bearer: %{ + type: "apiKey", + name: "Authorization", + description: "API Token must be provided via `Authorization: Bearer ` header", + in: "header" + } + }, + consumes: ["application/json"], + produces: ["application/json"], + tags: [] + } + end +end diff --git a/apps/rest_api/lib/routers/public.ex b/apps/rest_api/lib/routers/public.ex deleted file mode 100644 index bdb94b8..0000000 --- a/apps/rest_api/lib/routers/public.ex +++ /dev/null @@ -1,42 +0,0 @@ -defmodule RestAPI.Routers.Public do - @moduledoc false - - use RestAPI.Router - - alias RestAPI.Controllers.Public - alias RestAPI.Plugs.{Authentication, Authorization} - - pipeline :rest_api do - plug :accepts, ["json"] - end - - pipeline :authenticated do - plug Authentication - end - - pipeline :authorized_by_admin do - plug Authorization, type: "admin" - end - - scope "/api/v1", Public do - pipe_through :rest_api - - scope "/auth/protocol/openid-connect" do - post "/token", Auth, :sign_in - - scope "/" do - pipe_through :authenticated - - post "/logout", Auth, :sign_out - post "/logout-all-sessions", Auth, :sign_out_all_sessions - end - end - end - - scope "/admin/v1", RestAPI.Controller.Admin do - pipe_through :authenticated - pipe_through :authorized_by_admin - - resources("/users", User, except: [:new]) - end -end diff --git a/apps/rest_api/lib/swagger/auth_operations.ex b/apps/rest_api/lib/swagger/auth_operations.ex new file mode 100644 index 0000000..597a27f --- /dev/null +++ b/apps/rest_api/lib/swagger/auth_operations.ex @@ -0,0 +1,112 @@ +defmodule RestAPI.Swagger.AuthOperations do + @moduledoc false + + defmacro __using__(_opts) do + quote do + use PhoenixSwagger + + alias PhoenixSwagger.Schema + + ########### + # Requests + ########### + + @client_id "2e455bb1-0604-4812-9756-36f7ab23b8d9" + @client_secret "$2b$12$BSrTLJnb0Vfuk1iiSzw3MehAvgztbMYpnhneVLQhkoZbxAXBGUCFe" + @client_assertion "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJXYXRjaGVyRXgiLCJleHAiOjE2MDEyMzgwOTMsImlhdCI6MTYwMTIzMDg5MywiaXNzIjoiMmU0NTViYjEtMDYwNC00ODEyLTk3NTYtMzZmN2FiMjNiOGQ5IiwianRpIjoiMm9zYmUwc3JrbTMyc2tvN2ZrMDAwMnAzIiwibmJmIjoxNjAxMjMwODkzLCJ0eXAiOiJCZWFyZXIifQ.SDUlLMO9kVLfxyRRJUCCVPpz2fcjUtxC1K3IJPa2NrBp7S-IUGuZx9965M09jFJOZrNzqEC9VRZb9KqlZS2T0bGUg3pk8R91oqOgnPOvXEQ8bjTKuvqIv7K7hKaAARxRTgBf-o87quUoVoZzepLzfmJdnDVXy0QoFIO7_SYe4zmq3mrrvHM5Kaypgf0JMiOZORr2kEnk0zEkPoIvqL8psTrLlaUHr-cn3l3F7eGARhHijOTXoFXTH4BFjJzsQJRKcz1cyzUQ64Y02JWeYsbfi1higF14lGnFTduuVwMpqa7Wu5xK9FhmR1mmlqqFgD6NVeiDxoDcAzhhDbQWdKuuAyqyr67uYfY5qeeudoKYyJcjvfE0c1iMLpEQAlZDK_HjoChBEORcTcvbsCD-75y2lJhqsrW0cTWoqq0YTXU3SHvdewEZto8AEaQMKHnGozQQEkeF7rOFOJF7P_LX2LV7JbtxIl8RZPvjNNF6F6VHy_DJTVoJJNbIRRm47v8fXBBej60_76XZmxG_FtgZBevVgINq_lnYf2nb_2RybxyzRxfC4pRvTh6Og8mZy5fcgYIa4Yq3eXdDVAVxrFJWrJqfjdPSuZbFDuq6VfiXOAd_bNqNHMLN_jiTtJlVJnS-gk9Ejot8X-kwG-UPDoAQZIfyBqMSXIqyL-qFfVR8dIX9Dps" + + @sign_in_body %Schema{ + type: :object, + title: "Sign in body", + properties: %{ + username: %Schema{ + type: :string, + description: "Required on Resource Owner Flow", + example: "admin" + }, + password: %Schema{ + type: :string, + description: "Required on Resource Owner Flow", + example: "admin" + }, + client_id: %Schema{ + type: :string, + description: "Application Client ID", + required: true, + example: @client_id + }, + client_assertion: %Schema{ + type: :string, + description: "Required while using keys", + example: @client_assertion + }, + client_assertion_type: %Schema{ + type: :string, + description: "Required while using keys", + example: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + }, + client_secret: %Schema{ + type: :string, + description: "Only used when we don't have access to the keys", + example: @client_secret + }, + grant_type: %Schema{ + type: :string, + description: "Represent the authentication flow to be used", + example: "password" + }, + scope: %Schema{ + type: :string, + description: "Represent the scopes an subject is requesting", + example: "admin:read admin:write" + } + } + } + + ############ + # Responses + ############ + + @access_token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIyZTQ1NWJiMS0wNjA0LTQ4MTItOTc1Ni0zNmY3YWIyM2I4ZDkiLCJhenAiOiJhZG1pbiIsImV4cCI6MTYwMDc5NzU2NywiaWF0IjoxNjAwNzkwMzY3LCJpc3MiOiJXYXRjaGVyRXgiLCJqdGkiOiIyb3JpY210ODQ3NTg1ZHQ5YzgwMDAxcDEiLCJuYmYiOjE2MDA3OTAzNjcsInNjb3BlIjoiYWRtaW46cmVhZCBhZG1pbjp3cml0ZSIsInN1YiI6IjdmNWViOWRjLWI1NTAtNDU4Ni05MWRjLTNjNzAxZWIzYjliYyIsInR5cCI6IkJlYXJlciJ9.LWniDC38j2kW8ER8kgDnVVJO0eOXWGNq0KqXooMl-5s" + @refresh_token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdGkiOiIyb3JpY210ODQ3NTg1ZHQ5YzgwMDAxcDEiLCJhdWQiOiIyZTQ1NWJiMS0wNjA0LTQ4MTItOTc1Ni0zNmY3YWIyM2I4ZDkiLCJhenAiOiJhZG1pbiIsImV4cCI6MTYwMzM4MjM2NywiaWF0IjoxNjAwNzkwMzY3LCJpc3MiOiJXYXRjaGVyRXgiLCJqdGkiOiIyb3JpY210OG5vbjRkZHQ5YzgwMDAxcTEiLCJuYmYiOjE2MDA3OTAzNjcsInR5cCI6IkJlYXJlciJ9.U010q6KUB04K8rIU9rVnW_AOI1q5XSXSGIYdL1moaOA" + + @sign_in_response %Schema{ + type: :object, + title: "Sign in response", + properties: %{ + access_token: %Schema{ + type: :string, + description: "The access token that should be used on API", + example: @access_token + }, + refresh_token: %Schema{ + type: :string, + description: "The refresh token that should be used to get a new access_token", + example: @refresh_token + }, + expires_in: %Schema{ + type: :integer, + description: "When the access_token will be expired", + example: 7_200_000 + }, + token_type: %Schema{ + type: :string, + description: "The type of the access_token", + example: "Bearer" + } + } + } + + swagger_path :sign_in do + post("/api/v1/auth/protocol/openid-connect/token") + summary("Sign in the identity") + + parameters do + attributes(:body, @sign_in_body, "Request Body") + end + + response(200, "SUCCESS", @sign_in_response) + end + end + end +end diff --git a/apps/rest_api/mix.exs b/apps/rest_api/mix.exs index ed54b43..d58f19c 100644 --- a/apps/rest_api/mix.exs +++ b/apps/rest_api/mix.exs @@ -30,7 +30,7 @@ defmodule RestAPI.MixProject do def application do [ mod: {RestAPI.Application, []}, - extra_applications: [:logger, :runtime_tools] + extra_applications: [:logger, :runtime_tools, :phoenix_swagger] ] end @@ -55,6 +55,10 @@ defmodule RestAPI.MixProject do # Validations {:ecto_sql, "~> 3.4"}, + # Docs + {:phoenix_swagger, "~> 0.8"}, + {:ex_json_schema, "~> 0.5"}, + # Tools {:junit_formatter, "~> 3.1", only: [:test]}, {:dialyxir, "~> 1.0", only: :dev, runtime: false}, diff --git a/config/config.exs b/config/config.exs index 82018c6..c01936e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -2,6 +2,7 @@ import Config config :logger, :console, format: "$metadata[$level] $time $message\n", handle_sasl_reports: true config :phoenix, :json_library, Jason +config :phoenix_swagger, json_library: Jason # This is just for test purposes and should change in near future config :joken, default_signer: "secret" @@ -15,9 +16,9 @@ config :resource_manager, ecto_repos: [ResourceManager.Repo] config :resource_manager, ResourceManager.Application, children: [ ResourceManager.Repo, - # ResourceManager.Credentials.BlocklistPasswordCache, - # ResourceManager.Credentials.BlocklistPasswordManager, - # ResourceManager.Identities.Manager + ResourceManager.Credentials.BlocklistPasswordCache, + ResourceManager.Credentials.BlocklistPasswordManager, + ResourceManager.Identities.Manager ] config :resource_manager, ResourceManager.Repo, @@ -41,8 +42,8 @@ config :authenticator, Authenticator.Sessions.Cache, n_shards: 2, gc_interval: 3 config :authenticator, Authenticator.Application, children: [ Authenticator.Repo, - # Authenticator.Sessions.Cache - # Authenticator.Sessions.Manager + Authenticator.Sessions.Cache, + Authenticator.Sessions.Manager ] config :authenticator, Authenticator.Repo, @@ -72,4 +73,12 @@ config :rest_api, RestAPI.Ports.Authenticator, domain: Authenticator config :rest_api, RestAPI.Ports.Authorizer, domain: Authorizer config :rest_api, RestAPI.Ports.ResourceManager, domain: ResourceManager +config :rest_api, :phoenix_swagger, + swagger_files: %{ + "priv/static/swagger.json" => [ + router: RestAPI.Routers.Default, + endpoint: RestAPI.Endpoint + ] + } + import_config "#{Mix.env()}.exs" diff --git a/mix.exs b/mix.exs index 8a001ff..94083ff 100644 --- a/mix.exs +++ b/mix.exs @@ -68,8 +68,8 @@ defmodule WatcherEx.MixProject do defp dialyzer do [ plt_add_apps: [:ex_unit], - plt_core_path: "dialyzer/", - plt_file: {:no_warn, "dialyzer/watcher_ex.plt"} + plt_core_path: "dialyzer/plts/", + plt_file: {:no_warn, "dialyzer/plts/watcher_ex.plt"} ] end diff --git a/mix.lock b/mix.lock index ead038f..1ddb89e 100644 --- a/mix.lock +++ b/mix.lock @@ -18,6 +18,7 @@ "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"}, + "ex_json_schema": {:hex, :ex_json_schema, "0.7.4", "09eb5b0c8184e5702bc89625a9d0c05c7a0a845d382e9f6f406a0fc1c9a8cc3f", [:mix], [], "hexpm", "45c67fa840f0d719a2b5578126dc29bcdc1f92499c0f61bcb8a3bcb5935f9684"}, "excoveralls": {:hex, :excoveralls, "0.13.1", "b9f1697f7c9e0cfe15d1a1d737fb169c398803ffcbc57e672aa007e9fd42864c", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b4bb550e045def1b4d531a37fb766cbbe1307f7628bf8f0414168b3f52021cce"}, "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, @@ -37,6 +38,7 @@ "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, "phoenix": {:hex, :phoenix, "1.5.4", "0fca9ce7e960f9498d6315e41fcd0c80bfa6fbeb5fa3255b830c67fdfb7e703f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4e516d131fde87b568abd62e1b14aa07ba7d5edfd230bab4e25cc9dedbb39135"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, + "phoenix_swagger": {:hex, :phoenix_swagger, "0.8.2", "cc49d9641d7e7c87766ba800110ff67d2fb55379f83982ee33d85d1e0b39d100", [:mix], [{:ex_json_schema, "~> 0.6", [hex: :ex_json_schema, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "e6d177764d75d388b199a863c5f7502ac8c202cd3fca61220807cbdcb31efef2"}, "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.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", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"}, "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},