diff --git a/apps/ex_fleet_yards/lib/ex_fleet_yards/repo/account/oauth_client.ex b/apps/ex_fleet_yards/lib/ex_fleet_yards/repo/account/oauth_client.ex index 1b54780a..b3782422 100644 --- a/apps/ex_fleet_yards/lib/ex_fleet_yards/repo/account/oauth_client.ex +++ b/apps/ex_fleet_yards/lib/ex_fleet_yards/repo/account/oauth_client.ex @@ -20,7 +20,8 @@ defmodule ExFleetYards.Repo.Account.OauthClient do def create(user, %Boruta.Ecto.Client{id: id}), do: create(user, id) def create(%User{id: user_id}, client), do: create(user_id, client) - def create(user_id, %Ecto.Changeset{} = client_changeset) when is_binary(user_id) do + def create(user_id, %Ecto.Changeset{} = client_changeset) + when is_binary(user_id) or is_nil(user_id) do if client_changeset.valid? do Repo.transaction(fn -> with {:ok, client} <- Repo.insert(client_changeset), @@ -101,6 +102,7 @@ defmodule ExFleetYards.Repo.Account.OauthClient do id_token_signing_alg: "RS256", pkce: false } + def default_args, do: @default_args defp transform_client_args(args, true) do args = diff --git a/apps/ex_fleet_yards/lib/ex_fleet_yards/schemas.ex b/apps/ex_fleet_yards/lib/ex_fleet_yards/schemas.ex index deb94c97..782f186c 100644 --- a/apps/ex_fleet_yards/lib/ex_fleet_yards/schemas.ex +++ b/apps/ex_fleet_yards/lib/ex_fleet_yards/schemas.ex @@ -20,4 +20,91 @@ defmodule ExFleetYards.Schemas do example: %{count: 3, offset: 1, limit: 25, total: 4} }) end + + defmodule Result do + @moduledoc false + require OpenApiSpex + + @common_args + + OpenApiSpex.schema(%{ + description: "Generic result", + type: :object, + properties: %{ + code: %Schema{type: :string}, + message: %Schema{type: :string} + }, + required: [:code, :message], + example: %{code: "ok", message: "Operation Successfull"} + }) + end + + # defmacro result(name, description, properties) do + # properties = Macro.expand_once(properties, __CALLER__) + # |> Map.put(:code, %Schema{type: :string}) + # |> Map.put(:message, %Schema{type: :string}) + # |> IO.inspect() + + # quote do + # defmodule unquote(name) do + # @moduledoc unquote(description) + # require OpenApiSpex + + # OpenApiSpex.schema(%{ + # description: unquote(description), + # type: :object, + # properties: unquote(Macro.escape(properties)), + # required: [:code, :message], + # example: %{code: "ok", message: "Operation Successfull"} + # }) + # end + # end + # end + defmacro result(name, description, properties, required) when is_list(required) do + {properties_expanded, _} = + properties + |> Macro.expand_once(__CALLER__) + |> Macro.to_string() + |> Code.eval_string() + + properties_with_defaults = + Map.put_new(properties_expanded, :code, %Schema{type: :string}) + |> Map.put_new(:message, %Schema{type: :string}) + + required = Enum.uniq(required ++ [:code, :message]) + + quote do + defmodule unquote(name) do + @moduledoc unquote(description) + require OpenApiSpex + + OpenApiSpex.schema(%{ + description: unquote(description), + type: :object, + properties: unquote(Macro.escape(properties_with_defaults)), + required: unquote(required), + example: %{code: "ok", message: "Operation Successful"} + }) + end + end + end + + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end + + def controller do + quote do + use OpenApiSpex.ControllerSpecs + alias unquote(__MODULE__).Result + end + end + + def schema do + quote do + alias OpenApiSpex.Schema + require unquote(__MODULE__) + import unquote(__MODULE__), only: [result: 4] + end + end end diff --git a/apps/ex_fleet_yards/priv/repo/migrations/20230730111440_no_user_for_oauth_client.exs b/apps/ex_fleet_yards/priv/repo/migrations/20230730111440_no_user_for_oauth_client.exs new file mode 100644 index 00000000..ae3d0c29 --- /dev/null +++ b/apps/ex_fleet_yards/priv/repo/migrations/20230730111440_no_user_for_oauth_client.exs @@ -0,0 +1,14 @@ +defmodule ExFleetYards.Repo.Migrations.NoUserForOauthClient do + use Ecto.Migration + + def up do + execute "ALTER TABLE oauth_user_clients DROP CONSTRAINT oauth_user_clients_user_id_fkey" + + alter table(:oauth_user_clients) do + modify :user_id, references(:users, type: :uuid, on_delete: :delete_all), null: true + end + end + + def down do + end +end diff --git a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth.ex b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth.ex index 8d4e17b5..ddb2f8af 100644 --- a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth.ex +++ b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth.ex @@ -34,6 +34,27 @@ defmodule ExFleetYardsAuth do end end + def controller_api do + quote do + @moduledoc "Controller used for Auth" + use Phoenix.Controller, + formats: [:html, :json], + layouts: [html: ExFleetYardsAuth.Layouts] + + use ExFleetYards.Schemas, :controller + + import Plug.Conn + + alias ExFleetYards.Repo + + alias ExFleetYardsAuth.Router.Helpers, as: Routes + + unquote(verified_routes()) + + import ExFleetYards.Plugs.ApiAuthorization, only: [authorize: 2] + end + end + def live_view do quote do use Phoenix.LiveView, diff --git a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/api_spec.ex b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/api_spec.ex new file mode 100644 index 00000000..12418b94 --- /dev/null +++ b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/api_spec.ex @@ -0,0 +1,64 @@ +defmodule ExFleetYardsAuth.ApiSpec do + @moduledoc """ + OpenApi Spec definition root + """ + use ExFleetYardsAuth, :verified_routes + + alias OpenApiSpex.{ + Components, + Info, + OpenApi, + Paths, + Server, + SecurityScheme, + OAuthFlows, + OAuthFlow + } + + alias ExFleetYardsAuth.{Endpoint, Router} + @behaviour OpenApi + + @impl OpenApi + def spec do + %OpenApi{ + servers: [ + Server.from_endpoint(Endpoint) + ], + info: %Info{ + title: "Fleetyards", + version: ExFleetYards.Version.version() + }, + paths: Paths.from_router(Router), + components: %Components{ + securitySchemes: %{ + "authorization" => %SecurityScheme{ + type: "oauth2", + scheme: "bearer", + in: "header", + flows: %OAuthFlows{ + authorizationCode: %OAuthFlow{ + authorizationUrl: Endpoint.url() <> ~p"/oauth/authorize", + tokenUrl: Endpoint.url() <> ~p"/oauth/token", + scopes: scope_list() + }, + implicit: %OAuthFlow{ + authorizationUrl: Endpoint.url() <> ~p"/oauth/authorize", + scopes: scope_list() + } + } + } + } + } + } + |> OpenApiSpex.resolve_schema_modules() + end + + defp scope_list do + ExFleetYards.Scopes.scope_list() + |> Enum.map(fn + {scope, description} -> {to_string(scope), description} + {scope, description, _} -> {to_string(scope), description} + end) + |> Enum.into(%{}) + end +end diff --git a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/client_controller.ex b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/client_controller.ex index 234b1705..120843da 100644 --- a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/client_controller.ex +++ b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/client_controller.ex @@ -2,15 +2,23 @@ defmodule ExFleetYardsAuth.Api.ClientController do @moduledoc """ Boruta Client controller """ - use ExFleetYardsAuth, :controller + use ExFleetYardsAuth, :controller_api require Logger - import ExFleetYards.Plugs.ApiAuthorization, only: [authorize: 2] alias ExFleetYards.Repo alias ExFleetYards.Repo.Account.User alias ExFleetYards.Repo.Account.OauthClient + alias ExFleetYardsAuth.Api.ClientSchema plug(:authorize, ["user:security"]) + security [%{"authorization" => ["user:security"]}] + tags ["user", "security"] + + operation :index, + summary: "Return clients owned by user", + responses: [ + ok: {"ClientList", "application/json", ClientSchema.ClientList} + ] def index(conn, _params) do user = @@ -21,6 +29,19 @@ defmodule ExFleetYardsAuth.Api.ClientController do |> render("index.json", clients: user.oauth_clients) end + operation :get, + summary: "Get specific client", + parameters: [ + id: [ + in: :path, + description: "Id of client", + type: %OpenApiSpex.Schema{type: :string, format: :uuid} + ] + ], + responses: [ + ok: {"Client", "application/json", ClientSchema.Client} + ] + def get(conn, %{"id" => id}) do user = conn.assigns[:current_user] @@ -32,6 +53,13 @@ defmodule ExFleetYardsAuth.Api.ClientController do |> render("client.json", client: client) end + operation :post, + summary: "Create client", + request_body: {"Client", "application/json", ClientSchema.Client}, + responses: [ + created: {"Client", "application/json", ClientSchema.Client} + ] + def post(conn, params) do user = conn.assigns[:current_user] @@ -50,6 +78,20 @@ defmodule ExFleetYardsAuth.Api.ClientController do end end + operation :patch, + summary: "Update client", + parameters: [ + id: [ + in: :path, + description: "Id of client", + type: %OpenApiSpex.Schema{type: :string, format: :uuid} + ] + ], + request_body: {"Client", "application/json", ClientSchema.Client}, + responses: [ + ok: {"Client", "application/json", ClientSchema.Client} + ] + def patch(conn, %{"id" => id} = params) do user = conn.assigns[:current_user] @@ -68,6 +110,19 @@ defmodule ExFleetYardsAuth.Api.ClientController do end end + operation :delete, + summary: "Delete a client", + parameters: [ + id: [ + in: :path, + description: "Id of client", + type: %OpenApiSpex.Schema{type: :string, format: :uuid} + ] + ], + responses: [ + ok: {"ClientDelete", "application/json", ClientSchema.ClientDelete} + ] + def delete(conn, %{"id" => id}) do user = conn.assigns[:current_user] diff --git a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/client_schema.ex b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/client_schema.ex new file mode 100644 index 00000000..723a7ad2 --- /dev/null +++ b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/client_schema.ex @@ -0,0 +1,40 @@ +defmodule ExFleetYardsAuth.Api.ClientSchema do + @moduledoc """ + Schema definitions for Oauth Clients + """ + use ExFleetYards.Schemas, :schema + + defmodule Client do + require OpenApiSpex + + OpenApiSpex.schema(%{ + description: "Oauth Client", + type: :object, + properties: %{ + access_token_ttl: %Schema{type: :integer, example: 86400}, + authorization_code_ttl: %Schema{type: :integer, example: 60}, + id: %Schema{type: :string, format: :uuid}, + id_token_ttl: %Schema{type: :integer, example: 86400}, + name: %Schema{type: :string}, + pkce: %Schema{type: :boolean}, + redirect_uris: %Schema{type: :array, items: %Schema{type: :string, format: :uri}}, + refresh_token_ttl: %Schema{type: :integer, example: 86400}, + supported_grant_types: %Schema{type: :array, items: %Schema{type: :string}}, + secret: %Schema{type: :string} + }, + required: [:id, :name] + }) + end + + defmodule ClientList do + require OpenApiSpex + + OpenApiSpex.schema(%{ + description: "List of Oauth Clients", + type: :array, + items: Client + }) + end + + result(ClientDelete, "Client Delete", %{client: ExFleetYardsAuth.Api.ClientSchema.Client}, []) +end diff --git a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/totp_controller.ex b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/totp_controller.ex index 124eac79..212b5ccf 100644 --- a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/totp_controller.ex +++ b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/totp_controller.ex @@ -2,13 +2,21 @@ defmodule ExFleetYardsAuth.Api.TotpController do @moduledoc """ Totp controller """ - use ExFleetYardsAuth, :controller + use ExFleetYardsAuth, :controller_api require Logger - import ExFleetYards.Plugs.ApiAuthorization, only: [authorize: 2] + alias ExFleetYardsAuth.Api.TotpSchema alias ExFleetYards.Repo.Account.User plug(:authorize, ["user:security"]) + security [%{"authorization" => ["user:security"]}] + tags ["user", "security"] + + operation :index, + summary: "Returns if user has totp setup", + responses: [ + ok: {"UserHasTotp", "application/json", TotpSchema.UserHasTotp} + ] def index(conn, _params) do user = conn.assigns[:current_user] @@ -19,6 +27,13 @@ defmodule ExFleetYardsAuth.Api.TotpController do |> json(%{has_totp: totp}) end + operation :delete, + summary: "Delete totp for user", + responses: [ + ok: {"Result", "application/json", Result}, + not_found: {"Result", "application/json", Result} + ] + def delete(conn, _params) do user = conn.assigns[:current_user] @@ -38,12 +53,19 @@ defmodule ExFleetYardsAuth.Api.TotpController do end end + operation :create, + summary: "Create totp secret for user", + responses: [ + ok: {"TotpSecret", "application/json", TotpSchema.TotpSecret}, + bad_request: {"Result", "application/json", Result} + ] + def create(conn, _params) do user = conn.assigns[:current_user] if ExFleetYards.Repo.Account.User.Totp.exists?(user.id) do conn - |> put_status(400) + |> put_status(:bad_request) |> json(%{"code" => "already_exists", "message" => "totp already exists"}) else totp = @@ -55,6 +77,13 @@ defmodule ExFleetYardsAuth.Api.TotpController do end end + operation :put, + summary: "Put totp secret for user", + responses: [ + created: {"TotpRecovery", "application/json", TotpSchema.TotpRecovery}, + bad_request: {"Result", "application/json", Result} + ] + def put(conn, %{"secret" => secret}) do user = conn.assigns[:current_user] @@ -71,12 +100,12 @@ defmodule ExFleetYardsAuth.Api.TotpController do else true -> conn - |> put_status(400) + |> put_status(:bad_request) |> json(%{"code" => "already_exists", "message" => "totp already exists"}) :error -> conn - |> put_status(400) + |> put_status(:bad_request) |> json(%{"code" => "invalid_secret", "message" => "invalid secret"}) end end diff --git a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/totp_schema.ex b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/totp_schema.ex new file mode 100644 index 00000000..88fcb528 --- /dev/null +++ b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/api/totp_schema.ex @@ -0,0 +1,38 @@ +defmodule ExFleetYardsAuth.Api.TotpSchema do + @moduledoc """ + Schema definitions for TOTP + """ + use ExFleetYards.Schemas, :schema + + defmodule UserHasTotp do + require OpenApiSpex + + OpenApiSpex.schema(%{ + description: "User has totp", + type: :object, + properties: %{ + has_totp: %Schema{type: :boolean} + }, + required: [:has_totp], + example: %{has_totp: true} + }) + end + + ExFleetYards.Schemas.result( + TotpSecret, + "Totp secret", + %{ + secret: %OpenApiSpex.Schema{type: :string} + }, + [:secret] + ) + + result( + TotpRecovery, + "Totp Recovery", + %{ + recovery_codes: %OpenApiSpex.Schema{type: :array, items: %OpenApiSpex.Schema{type: :string}} + }, + [:recovery_codes] + ) +end diff --git a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/error/error_html/503.html.heex b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/error/error_html/503.html.heex new file mode 100644 index 00000000..dded97d5 --- /dev/null +++ b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/controllers/error/error_html/503.html.heex @@ -0,0 +1,22 @@ +
+
+ Logo + +

+ Internal Server Error +

+
+ +
+
+

+ An unexpected error occurred while processing your request. +

+ +
+
+
diff --git a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/release/oauth_client.ex b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/release/oauth_client.ex new file mode 100644 index 00000000..6c97858b --- /dev/null +++ b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/release/oauth_client.ex @@ -0,0 +1,47 @@ +defmodule ExFleetYardsAuth.Release.OauthClient do + @moduledoc """ + Helper to create static Oauth Clients + """ + + alias ExFleetYards.Repo + alias ExFleetYardsAuth.Endpoint + use ExFleetYardsAuth, :verified_routes + + # Swagger ui + def swagger_ui_uuid, do: "52d96be7-8e29-412b-a9f2-05a532ff9708" + + def swagger_ui_callback_uri do + Endpoint.url() <> ~p"/api/oauth2-redirect.html" + end + + def get_or_create_swagger_client_id() do + id = swagger_ui_uuid() + import Ecto.Query + + Repo.exists?(from(c in Boruta.Ecto.Client, where: c.id == ^id)) + |> case do + true -> + id + + false -> + create_client(id, "Fleetyards Auth API", [swagger_ui_callback_uri()]) + end + end + + def create_client(id \\ nil, name, redirect_uris) do + id = if(is_nil(id), do: SecureRandom.uuid(), else: id) + + args = + Map.merge(Repo.Account.OauthClient.default_args(), %{ + id: id, + name: name, + authorize_scopes: true, + redirect_uris: redirect_uris + }) + + client_changeset = Boruta.Ecto.Client.create_changeset(%Boruta.Ecto.Client{}, args) + + {:ok, client} = Repo.Account.OauthClient.create(nil, client_changeset) + client.client.id + end +end diff --git a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/router.ex b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/router.ex index f3266312..65978a5a 100644 --- a/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/router.ex +++ b/apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/router.ex @@ -23,6 +23,7 @@ defmodule ExFleetYardsAuth.Router do pipeline :api do plug :accepts, ["json"] + plug OpenApiSpex.Plug.PutApiSpec, module: ExFleetYardsAuth.ApiSpec end scope "/" do @@ -108,7 +109,29 @@ defmodule ExFleetYardsAuth.Router do end end - scope "/api", ExFleetYardsAuth.Api do + scope "/api", OpenApiSpex.Plug do + scope "/v2/openapi" do + pipe_through :api + get "/", RenderSpec, [] + end + + scope "/" do + pipe_through :browser + + get "/docs", SwaggerUI, + path: "/api/v2/openapi", + persist_authorization: true, + oauth: [ + app_name: "Fleetyards Auth API", + client_id: ExFleetYardsAuth.Release.OauthClient.swagger_ui_uuid(), + scopes: ["openid", "profile", "user", "user:security"] + ] + + get "/oauth2-redirect.html", SwaggerUIOAuth2Redirect, [] + end + end + + scope "/api/v2", ExFleetYardsAuth.Api do pipe_through :api scope "/totp" do diff --git a/apps/ex_fleet_yards_auth/mix.exs b/apps/ex_fleet_yards_auth/mix.exs index 439f3277..c5a31bdf 100644 --- a/apps/ex_fleet_yards_auth/mix.exs +++ b/apps/ex_fleet_yards_auth/mix.exs @@ -39,6 +39,7 @@ defmodule ExFleetYardsAuth.MixProject do {:phoenix_live_view, "~> 0.18.18"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, {:remote_ip, "~> 1.1"}, + {:open_api_spex, "~> 3.16"}, # Auth {:wax_, "~> 0.6.0"}, diff --git a/apps/ex_fleet_yards_auth/test/ex_fleet_yards_auth/unit/api/controllers/client_controller_test.exs b/apps/ex_fleet_yards_auth/test/ex_fleet_yards_auth/unit/api/controllers/client_controller_test.exs index 6f7555f4..206a4e95 100644 --- a/apps/ex_fleet_yards_auth/test/ex_fleet_yards_auth/unit/api/controllers/client_controller_test.exs +++ b/apps/ex_fleet_yards_auth/test/ex_fleet_yards_auth/unit/api/controllers/client_controller_test.exs @@ -1,24 +1,26 @@ defmodule ExFleetYardsAuth.Api.ClientControllerTest do use ExFleetYardsAuth.ConnCase, async: true use ExFleetYardsAuth.Mox + import OpenApiSpex.TestAssertions setup :verify_on_exit! describe "oauth client" do - test "list clients", %{conn: conn} do + test "list clients", %{conn: conn, spec: spec} do login_user("testuser", "user:security") login_user("testuser", "user:security") login_user("testuser", "user:security") conn = conn - |> get(~p"/api/oauth/clients") + |> get(~p"/api/v2/oauth/clients") + assert_schema json_response(conn, 200), "ClientList", spec assert json_response(conn, 200) == [] conn = conn - |> post(~p"/api/oauth/clients", %{ + |> post(~p"/api/v2/oauth/clients", %{ "name" => "testclient", "redirect_uris" => ["https://example.com"], "access_token_ttl" => 3600, @@ -28,20 +30,22 @@ defmodule ExFleetYardsAuth.Api.ClientControllerTest do }) json = json_response(conn, 201) + assert_schema json, "Client", spec conn = conn - |> get(~p"/api/oauth/clients") + |> get(~p"/api/v2/oauth/clients") + assert_schema json_response(conn, 200), "ClientList", spec assert Enum.count(json_response(conn, 200)) == 1 end - test "create client", %{conn: conn} do + test "create client", %{conn: conn, spec: spec} do login_user("testuser", "user:security") conn = conn - |> post(~p"/api/oauth/clients", %{ + |> post(~p"/api/v2/oauth/clients", %{ "name" => "testclient", "redirect_uris" => ["https://example.com"], "access_token_ttl" => 3600, @@ -51,6 +55,7 @@ defmodule ExFleetYardsAuth.Api.ClientControllerTest do }) json = json_response(conn, 201) + assert_schema json, "Client", spec assert json["name"] == "testclient" assert json["redirect_uris"] == ["https://example.com"] assert json["pkce"] == false @@ -61,13 +66,13 @@ defmodule ExFleetYardsAuth.Api.ClientControllerTest do assert json["secret"] != nil end - test "update client", %{conn: conn} do + test "update client", %{conn: conn, spec: spec} do login_user("testuser", "user:security") login_user("testuser", "user:security") conn = conn - |> post(~p"/api/oauth/clients", %{ + |> post(~p"/api/v2/oauth/clients", %{ "name" => "testclient", "redirect_uris" => ["https://example.com"], "access_token_ttl" => 3600, @@ -77,14 +82,16 @@ defmodule ExFleetYardsAuth.Api.ClientControllerTest do }) json = json_response(conn, 201) + assert_schema json, "Client", spec conn = conn - |> patch(~p"/api/oauth/clients/" <> json["id"], %{ + |> patch(~p"/api/v2/oauth/clients/" <> json["id"], %{ "redirect_uris" => ["https://example.org"] }) json = json_response(conn, 200) + assert_schema json, "Client", spec assert json["name"] == "testclient" assert json["redirect_uris"] == ["https://example.org"] assert json["pkce"] == false @@ -95,13 +102,13 @@ defmodule ExFleetYardsAuth.Api.ClientControllerTest do assert json["secret"] == nil end - test "delete client", %{conn: conn} do + test "delete client", %{conn: conn, spec: spec} do login_user("testuser", "user:security") login_user("testuser", "user:security") conn = conn - |> post(~p"/api/oauth/clients", %{ + |> post(~p"/api/v2/oauth/clients", %{ "name" => "testclient", "redirect_uris" => ["https://example.com"], "access_token_ttl" => 3600, @@ -111,12 +118,14 @@ defmodule ExFleetYardsAuth.Api.ClientControllerTest do }) json = json_response(conn, 201) + assert_schema json, "Client", spec conn = conn - |> delete(~p"/api/oauth/clients/" <> json["id"]) + |> delete(~p"/api/v2/oauth/clients/" <> json["id"]) json = json_response(conn, 200) + assert_schema json, "ClientDelete", spec assert json["code"] == "ok" assert json["message"] == "client deleted" assert json["client"]["name"] == "testclient" diff --git a/apps/ex_fleet_yards_auth/test/ex_fleet_yards_auth/unit/api/controllers/totp_controller_test.exs b/apps/ex_fleet_yards_auth/test/ex_fleet_yards_auth/unit/api/controllers/totp_controller_test.exs index ab0112b3..ae60065b 100644 --- a/apps/ex_fleet_yards_auth/test/ex_fleet_yards_auth/unit/api/controllers/totp_controller_test.exs +++ b/apps/ex_fleet_yards_auth/test/ex_fleet_yards_auth/unit/api/controllers/totp_controller_test.exs @@ -1,52 +1,64 @@ defmodule ExFleetYardsAuth.Controllers.Api.TotpControllerTest do use ExFleetYardsAuth.ConnCase, async: true use ExFleetYardsAuth.Mox + import OpenApiSpex.TestAssertions setup :verify_on_exit! describe "totp" do - test "has no totp", %{conn: conn} do + test "has no totp", %{conn: conn, spec: spec} do login_user("testuser", "user:security") conn = conn - |> get(~p"/api/totp") + |> get(~p"/api/v2/totp") - assert json_response(conn, 200) == %{ + json = json_response(conn, 200) + + assert_schema json, "UserHasTotp", spec + + assert json == %{ "has_totp" => false } end - test "delete non existing totp", %{conn: conn} do + test "delete non existing totp", %{conn: conn, spec: spec} do login_user("testuser", "user:security") conn = conn - |> delete(~p"/api/totp") + |> delete(~p"/api/v2/totp") - assert json_response(conn, 404) == %{ + json = json_response(conn, 404) + + assert_schema json, "Result", spec + + assert json == %{ "code" => "not_found", "message" => "totp not found" } end - test "create totp", %{conn: conn} do + test "create totp", %{conn: conn, spec: spec} do login_user("testuser", "user:security") login_user("testuser", "user:security") login_user("testuser", "user:security") conn = conn - |> post(~p"/api/totp/create") + |> post(~p"/api/v2/totp/create") json = json_response(conn, 200) + assert_schema json, "TotpSecret", spec assert json["code"] == "ok" assert json["message"] == "totp secret" assert json["secret"] != nil conn = conn - |> delete(~p"/api/totp") + |> delete(~p"/api/v2/totp") + + assert_schema json_response(conn, 404), "Result", spec assert json_response(conn, 404) == %{ "code" => "not_found", @@ -55,7 +67,9 @@ defmodule ExFleetYardsAuth.Controllers.Api.TotpControllerTest do conn = conn - |> post(~p"/api/totp", %{"secret" => "invalid_base32_secret"}) + |> post(~p"/api/v2/totp", %{"secret" => "invalid_base32_secret"}) + + assert_schema json_response(conn, 400), "Result", spec assert json_response(conn, 400) == %{ "code" => "invalid_secret", diff --git a/apps/ex_fleet_yards_auth/test/support/conn_case.ex b/apps/ex_fleet_yards_auth/test/support/conn_case.ex index 53523cb1..daa07669 100644 --- a/apps/ex_fleet_yards_auth/test/support/conn_case.ex +++ b/apps/ex_fleet_yards_auth/test/support/conn_case.ex @@ -40,6 +40,6 @@ defmodule ExFleetYardsAuth.ConnCase do conn = Phoenix.ConnTest.build_conn() - {:ok, conn: conn} + {:ok, conn: conn, spec: ExFleetYardsAuth.ApiSpec.spec()} end end