defmodule Boruta.OauthModule do
@moduledoc false
alias Boruta.Oauth.ResourceOwner
@callback token(conn :: Plug.Conn.t() | map(), module :: atom()) :: any()
@callback preauthorize(conn :: Plug.Conn.t() | map(), resource_owner :: ResourceOwner.t(), module :: atom()) :: any()
@callback authorize(conn :: Plug.Conn.t() | map(), resource_owner :: ResourceOwner.t(), module :: atom()) :: any()
@callback introspect(conn :: Plug.Conn.t() | map(), module :: atom()) :: any()
@callback revoke(conn :: Plug.Conn.t() | map(), module :: atom()) :: any()
end
defmodule Boruta.Oauth do
@moduledoc """
Boruta OAuth entrypoint, handles OAuth requests.
> Note : this module follows inverted heaxagonal architecture, its functions will invoke functions of the given argument module as result.
> The definition of the callbacks are provided by either `Boruta.Oauth.Application` or `Boruta.Oauth.AuthorizeApplication`, `Boruta.Oauth.TokenApplication`, `Boruta.Oauth.IntrospectApplication`, and `Boruta.Oauth.RevokeApplication`,
"""
@behaviour Boruta.OauthModule
alias Boruta.Oauth.Authorization
alias Boruta.Oauth.AuthorizeResponse
alias Boruta.Oauth.Error
alias Boruta.Oauth.Introspect
alias Boruta.Oauth.IntrospectResponse
alias Boruta.Oauth.Request
alias Boruta.Oauth.ResourceOwner
alias Boruta.Oauth.Revoke
alias Boruta.Oauth.TokenResponse
@doc """
Process an token request as stated in [RFC 6749 - The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749).
Triggers `token_success` in case of success and `token_error` in case of failure from the given `module`. Those functions are described in `Boruta.Oauth.Application` behaviour.
"""
@spec token(conn :: Plug.Conn.t() | map(), module :: atom()) :: any()
@impl true
def token(%Plug.Conn{} = conn, module) when is_atom(module) do
with {:ok, request} <- Request.token_request(conn),
{:ok, tokens} <- Authorization.token(request),
%TokenResponse{} = response <- TokenResponse.from_token(tokens) do
module.token_success(
conn,
response
)
else
{:error, %Error{} = error} ->
module.token_error(conn, error)
{:error, reason} ->
error = %Error{
status: :internal_server_error,
error: :unknown_error,
error_description: inspect(reason)
}
module.token_error(conn, error)
end
end
@doc """
Process an authorize request as stated in [RFC 6749 - The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749).
Triggers `preauthorize_success` in case of success and `preauthorize_error` in case of failure from the given `module`. Those functions are described in `Boruta.Oauth.Application` behaviour.
"""
@spec preauthorize(conn :: Plug.Conn.t() | map(), resource_owner :: ResourceOwner.t(), module :: atom()) :: any()
@impl true
def preauthorize(%Plug.Conn{} = conn, %ResourceOwner{} = resource_owner, module) when is_atom(module) do
with {:ok, request} <- Request.authorize_request(conn, resource_owner),
{:ok, authorization} <- Authorization.preauthorize(request) do
module.preauthorize_success(
conn,
authorization
)
else
{:error, %Error{} = error} ->
case Request.authorize_request(conn, resource_owner) do
{:ok, request} ->
module.preauthorize_error(conn, Error.with_format(error, request))
_ ->
module.preauthorize_error(conn, error)
end
end
end
@doc """
Process an authorize request and returns a token as stated in [RFC 6749 - The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749).
Triggers `authorize_success` in case of success and `authorize_error` in case of failure from the given `module`. Those functions are described in `Boruta.Oauth.Application` behaviour.
"""
@spec authorize(conn :: Plug.Conn.t() | map(), resource_owner :: ResourceOwner.t(), module :: atom()) :: any()
@impl true
def authorize(%Plug.Conn{} = conn, %ResourceOwner{} = resource_owner, module) when is_atom(module) do
with {:ok, request} <- Request.authorize_request(conn, resource_owner),
{:ok, tokens} <- Authorization.token(request),
%AuthorizeResponse{} = response <- AuthorizeResponse.from_tokens(tokens) do
module.authorize_success(
conn,
response
)
else
{:error, %Error{} = error} ->
formatted_authorize_error(conn, resource_owner, module, error)
{:error, reason} ->
error = %Error{
status: :internal_server_error,
error: :unknown_error,
error_description: inspect(reason)
}
formatted_authorize_error(conn, resource_owner, module, error)
end
end
defp formatted_authorize_error(conn, resource_owner, module, error) do
case Request.authorize_request(conn, resource_owner) do
{:ok, request} ->
module.authorize_error(conn, Error.with_format(error, request))
_ ->
module.authorize_error(conn, error)
end
end
@doc """
Process a introspect request as stated in [RFC 7662 - OAuth 2.0 Token Introspection](https://tools.ietf.org/html/rfc7662).
Triggers `introspect_success` in case of success and `introspect_error` in case of failure from the given `module`. Those functions are described in `Boruta.Oauth.Application` behaviour.
"""
@spec introspect(conn :: Plug.Conn.t() | map(), module :: atom()) :: any()
@impl true
def introspect(%Plug.Conn{} = conn, module) when is_atom(module) do
with {:ok, request} <- Request.introspect_request(conn),
{:ok, token} <- Introspect.token(request) do
module.introspect_success(conn, IntrospectResponse.from_token(token))
else
{:error, %Error{error: :invalid_access_token} = error} ->
module.introspect_success(conn, IntrospectResponse.from_error(error))
{:error, %Error{} = error} ->
module.introspect_error(conn, error)
end
end
@doc """
Process a revoke request as stated in [RFC 7009 - OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009).
Triggers `revoke_success` in case of success and `revoke_error` in case of failure from the given `module`. Those functions are described in `Boruta.Oauth.Application` behaviour.
"""
@spec revoke(conn :: Plug.Conn.t() | map(), module :: atom()) :: any()
@impl true
def revoke(%Plug.Conn{} = conn, module) when is_atom(module) do
with {:ok, request} <- Request.revoke_request(conn),
:ok <- Revoke.token(request) do
module.revoke_success(conn)
else
{:error, error} ->
module.revoke_error(conn, error)
{:error, reason} ->
error = %Error{
status: :internal_server_error,
error: :unknown_error,
error_description: inspect(reason)
}
module.revoke_error(conn, error)
end
end
end