Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): add openapi #89

Merged
merged 4 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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 =
Expand Down
87 changes: 87 additions & 0 deletions apps/ex_fleet_yards/lib/ex_fleet_yards/schemas.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
64 changes: 64 additions & 0 deletions apps/ex_fleet_yards_auth/lib/ex_fleet_yards_auth/api_spec.ex
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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]

Expand All @@ -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]

Expand All @@ -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]

Expand All @@ -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]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule ExFleetYardsAuth.Api.ClientSchema do
@moduledoc """
Schema definitions for Oauth Clients
"""
use ExFleetYards.Schemas, :schema

defmodule Client do

Check warning

Code scanning / Credo

Modules should have a @moduledoc tag. Warning

Modules should have a @moduledoc tag.
require OpenApiSpex

OpenApiSpex.schema(%{
description: "Oauth Client",
type: :object,
properties: %{
access_token_ttl: %Schema{type: :integer, example: 86400},

Check failure

Code scanning / Credo

Numbers larger than 9999 should be written with underscores: 86_400 Error

Numbers larger than 9999 should be written with underscores: 86_400
authorization_code_ttl: %Schema{type: :integer, example: 60},
id: %Schema{type: :string, format: :uuid},
id_token_ttl: %Schema{type: :integer, example: 86400},

Check failure

Code scanning / Credo

Numbers larger than 9999 should be written with underscores: 86_400 Error

Numbers larger than 9999 should be written with underscores: 86_400
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},

Check failure

Code scanning / Credo

Numbers larger than 9999 should be written with underscores: 86_400 Error

Numbers larger than 9999 should be written with underscores: 86_400
supported_grant_types: %Schema{type: :array, items: %Schema{type: :string}},
secret: %Schema{type: :string}
},
required: [:id, :name]
})
end

defmodule ClientList do

Check warning

Code scanning / Credo

Modules should have a @moduledoc tag. Warning

Modules should have a @moduledoc tag.
require OpenApiSpex

OpenApiSpex.schema(%{
description: "List of Oauth Clients",
type: :array,
items: Client
})
end

result(ClientDelete, "Client Delete", %{client: ExFleetYardsAuth.Api.ClientSchema.Client}, [])
end
Loading
Loading