README.md

# Speakeasy

Middleware based authentication and authorization for [Absinthe](https://hexdocs.pm/absinthe) GraphQL powered by [Bodyguard](https://hexdocs.pm/bodyguard).

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `speakeasy` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:speakeasy, "~> 0.3.0"}
  ]
end
```

## Configuration

Configuration can be done in each Absinthe middleware call, but you can set global defaults as well.

```elixir
config :speakeasy,
  user_key: :current_user,                # the key the current user will be under in the GraphQL context
  authn_error_message: :unauthenticated  # default authentication failure message
```

_Note:_ no `authz_error_message` is provided because it is set from Bodyguard.

## Usage

**tl;dr:** A full example authentication, authorizing, loading, and resolving an Absinthe schema:

_This example assumes:_

- You are authorizing a standard phoenix context
- You already have a [bodyguard policy](https://github.com/schrockwell/bodyguard#policies)
- Your `:current_user` is already in the Absinthe context _or_ you are using [`Speakeasy.Plug`](#speakeasy-plug)

```elixir
defmodule MyApp.Schema.PostTypes do
  use Absinthe.Schema.Notation
  alias Spectra.Posts

  object :post do
    field(:id, non_null(:id))
    field(:name, non_null(:string))
  end

  object :post_mutations do
    @desc "Create post"
    field :create_post, type: :post do
      arg(:name, non_null(:string))
      middleware(Speakeasy.Authn)
      middleware(Speakeasy.Authz, {Posts, :create_post})
      middleware(Speakeasy.Resolve, &Posts.create_post/2)
      middleware(MyApp.Middleware.ChangesetErrors) # :D
    end

    @desc "Update post"
    field :update_post, type: :post do
      arg(:name, non_null(:string))
      middleware(Speakeasy.Authn)
      middleware(Speakeasy.Authz, {Posts, :update_post})
      middleware(Speakeasy.Resolve, &Posts.update_post/3)
      middleware(MyApp.Middleware.ChangesetErrors) # :D
    end
  end

  object :post_queries do
    @desc "Get posts"
    field :posts, list_of(:post) do
      middleware(Speakeasy.Authn)
      middleware(Speakeasy.Resolve, fn(attrs, user) -> MyApp.Posts.search(attrs, user) end)
    end

    @desc "Get post"
    field :post, type: :post do
      arg(:id, non_null(:string))
      middleware(Speakeasy.Authn)
      middleware(Speakeasy.LoadResourceByID, &Posts.get_post/1)
      middleware(Speakeasy.Authz, {Posts, :get_post})
      middleware(Speakeasy.Resolve)
    end
  end
end
```

### Middleware

Speakeasy is a collection of Absinthe middlewares:

- [Speakeasy.Authn]()

- [Speakeasy.LoadResource]()

- [Speakeasy.LoadResourceById]()

- [Speakeasy.AuthZ]()

- [Speakeasy.Resolve]()

### Speakeasy.Plug

Speakeasy includes a Plug for loading the current user into the Absinthe context. It isn't required if you already have a method for loading the user into your Absinthe context.

```elixir
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  pipeline :graphql do
    plug(Speakeasy.Plug, load_user: &MyApp.Users.whoami/1, user_key: :current_user)
  end
end
```