Packages
sentry
0.1.2
13.3.0
13.2.0
13.1.0
13.0.1
13.0.0
12.0.3
12.0.2
12.0.1
12.0.0
11.0.4
11.0.3
11.0.2
11.0.1
11.0.0
10.10.0
10.9.0
10.8.1
10.8.0
10.7.1
10.7.0
10.6.2
10.6.1
10.6.0
10.5.0
10.4.0
10.3.0
10.2.1
10.2.0
10.2.0-rc.2
10.2.0-rc.1
10.1.0
10.0.3
10.0.2
10.0.1
10.0.0
9.1.0
9.0.0
8.1.0
8.0.6
8.0.5
8.0.4
8.0.3
8.0.2
8.0.1
8.0.0
8.0.0-rc.2
8.0.0-rc.1
8.0.0-rc.0
retired
7.2.5
7.2.4
7.2.3
7.2.2
7.2.1
7.2.0
7.1.0
7.0.6
7.0.5
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.4.2
6.4.1
6.4.0
6.3.0
6.2.1
6.2.0
6.1.0
6.0.5
6.0.4
6.0.3
6.0.2
6.0.1
6.0.0
5.0.1
5.0.0
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.2.0
2.1.0
2.0.2
2.0.1
2.0.0
1.1.2
1.1.1
1.1.0
1.0.0
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
The Official Elixir client for Sentry
Current section
Files
Jump to
Current section
Files
README.md
# Sentry
"Sentry provides a set of helpers and conventions that will guide you in leveraging Elixir modules to build a simple, robust authorization system." - Inspired by [elabs/pundit](https://github.com/elabs/pundit)
## TODOs
- Generators
- JWT
## Installation
Add sentry to your list of dependencies in `mix.exs`:
```elixir
def deps do
[{:sentry, "~> 0.1"}]
end
```
Ensure sentry is started before your application:
```elixir
def application do
[applications: [:sentry]]
end
```
Ensure your `User` model and `users` table has the following fields:
- `:encrypted_password` field
- a user identification field. Defaults to `email`
- and a virtual password field. Defaults to `password`
```elixir
# web/models/user.ex
defmodule MyApp.User do
use MyApp.Web, :model
schema "users" do
field :email, :string
field :encrypted_password, :string
field :password, :string, virtual: true
field :password_confirmation, :string, virtual: true
...
timestamps
end
end
```
Configure Ueberauth and Sentry in `config/config.exs`
```elixir
# config/config.exs
# Ueberauth
config :ueberauth, Ueberauth,
providers: [
identity: {Ueberauth.Strategy.Identity, [
callback_methods: ["POST"]
]}
]
# Sentry
config :sentry, Sentry,
repo: MyApp.Repo,
model: MyApp.User # you may use a different model as you like
# uid_field: :some_id_field \\ defaults to :email
# password_field: :some_pw_field \\ defaults to :password
```
## Authentication
Sentry leverages [ueberauth](https://github.com/ueberauth/ueberauth) for the authentication layer with an addition of bcrypt encryption when storing and authenticating user.
Sentry provides useful helpers for working with users on your system
- `Sentry.Authenticator.attempt/1`
- `Sentry.Authenticator.logout/1`
- `Sentry.Authenticator.encrypt_password/1`
- `Sentry.Authenticator.logged_in?/1`
- `Sentry.Authenticator.current_user/1`
here's an example use case
```elixir
# web/controllers/auth_controller
defmodule MyApp.AuthController do
use MyApp.Web, :controller
use Sentry, :authenticator
alias MyApp.User
def request(conn, %{"provider" => "identity"} = _params) do
render(conn, callback_url: callback_url(conn),
changeset: User.changeset(%User{}))
end
def request(conn, _params) do
conn
|> put_status(:not_found)
|> render(MyApp.ErrorView, "404.html")
end
def callback(%{ assigns: %{ ueberauth_failure: fails } } = conn, _) do
conn
|> put_flash(:error, "Failed to authenticate.")
|> redirect(to: "/")
end
def callback(conn, %{"provider" => "identity"}) do
case attempt(conn) do
{:ok, conn} ->
conn
|> put_flash(:info, "You've successfully logged in")
|> redirect(to: "/")
{:error, reason} ->
conn
|> put_flash(:error, reason)
|> redirect(to: request_path(conn))
end
end
def delete(conn, _params) do
conn
|> put_flash(:info, "You've been logged out!")
|> logout()
|> redirect(to: "/")
end
end
```
```elixir
# web/router.ex
defmodule MyApp.Router do
...
pipeline :auth do
plug Ueberauth
end
scope "/auth", MyApp do
pipe_through [:browser, :auth]
get "/logout", AuthController, :delete
get "/:provider", AuthController, :request
get "/:provider/callback", AuthController, :callback
post "/:provider/callback", AuthController, :callback
end
end
```
```elixir
# web/view/layout_view.ex
defmodule MyApp.LayoutView do
use MyApp.Web, :view
use Sentry, :view # this adds the logged_in?/1 and current_user/1 helper to the view
end
```
```elixir
# web/templates/auth/request.html.eex
<%= form_for @changeset, @callback_url, fn f -> %>
<%= if f.errors != [] do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below:</p>
<ul>
<%= for {attr, message} <- f.errors do %>
<li><%= humanize(attr) %> <%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<label>Email</label>
<%= text_input f, :email, class: "form-control" %>
</div>
<div class="form-group">
<label>Password</label>
<%= password_input f, :password, class: "form-control" %>
</div>
<div class="form-group">
<%= submit "Login", class: "btn btn-primary" %>
</div>
<% end %>
```
## Authorization
For authorization, we have 3 macros for dealing with it
- `Sentry.Authorizer.authorize/2`
- `Sentry.Authorizer.authorize_changeset/2`
- `Sentry.Authorizer.authorize_changeset/3`
Let's say a `PostController.Create` action should only be authorized when a set of conditions returns true.
```elixir
# web/controllers/post_controller.ex
defmodule MyApp.PostController do
use Sentry, :authorizer
alias MyApp.Repo
alias MyApp.Post
def update(conn, %{"id" => id, "post" => post_params}) do
changeset = Repo.get!(Post, id)
|> Post.changeset(params)
authorize_changeset(conn, changeset) # you may optionally override the function to be executed on the policy module by passing a third argument. Example: :create
end
end
```
the `authorize_changeset/2` macro basically does this
```elixir
unless MyApp.PostPolicy.update(conn, changeset) do
raise Sentry.NotAuthorizedError
end
```
```elixir
# web/policies/post_policy.ex
defmodule MyApp.PostPolicy do
use Sentry, :authenticator
def update(conn, changeset) do
current_user = current_user(conn) # here we use Sentry.Authenticator.current_user helper to get the current user in the session
changeset.params.post["user_id"] === current_user.id # Only authorize when the post belongs to the current user
end
end
```
### Authorization without resource/changeset
If you are not working on a changeset/resource you may opt to use the `Sentry.Authorizer.authorize/2` instead, the second optional argument can be used to pass data to the policy action.
Do take note the `Sentry.Authorizer.authorize/2` will use the policy name based on the controller name.
For example: an action on `UserController.create` will use `UserPolicy.create`
```elixir
# web/controllers/user_controller.ex
defmodule UserController do
use Sentry, :authorizer
def create(conn, params) do
authorize(conn, params)
end
end
```
```elixir
# web/policies/user_policy.ex
defmodule UserPolicy do
def create(conn, opts) do
true
end
end
```
## Error Handling
To handle the errors raised by `Sentry.Authorizer.authorize`, we can use something like [Plug.ErrorHandler](http://hexdocs.pm/plug/Plug.ErrorHandler.html). This can either be plugged at the router level or controller.
## License
Sentry is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)