Skip to content

Commit

Permalink
Merge pull request #89 from fleetyards/auth/openapi
Browse files Browse the repository at this point in the history
feat(auth): add openapi
  • Loading branch information
kloenk authored Jul 30, 2023
2 parents 64dfc89 + b47f3f5 commit 838ef49
Show file tree
Hide file tree
Showing 16 changed files with 498 additions and 32 deletions.
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
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
Loading

0 comments on commit 838ef49

Please sign in to comment.