Sentinel

Things I wish Guardian included out of the box. Rather than make it so everyone has to redo this everytime, I created this package for it. It's really just a thin wrapper on Guardian, and some ground work, but really, everybody shouldn't have to repeat this themselves when they build stuff.

Shamelessly borrows from Devise and PhoenixTokenAuth.

If there are any ways that you feel that this library isn't "functional" or the code isn't written in "idiomatic Elixir" or whatever see the Contributing/Want something new? section.

Installation

Here's how to add it to your phoenix project, and things you need to setup:

# mix.exs
{:sentinel, "~> 0.0.2"},
# Add mailman as a peer dependency
#{:mailman, "~> 0.2.1"}
# OR
#{:mailman, github: "Joe-noh/mailman"}

The User Model

Your user model must have at least the following fields, email needs to be required, and you need to define a permissions function, which returns a map of permissions for the user when given a user as the input See the permissions section of guardian for more info.

defmodule MyApp.User do
use Ecto.Model
schema "users" do
field :email, :string # or :username
field :role, :string
field :hashed_password, :string
field :hashed_confirmation_token, :string
field :confirmed_at, Ecto.DateTime
field :hashed_password_reset_token, :string
field :unconfirmed_email, :string
end
@required_fields ~w(email)
@optional_fields ~w()
def changeset(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
def permissions(role) do
end
end

Configure Guardian

Example config:

config :guardian, Guardian,
allowed_algos: ["HS512"], # optional
verify_module: Guardian.JWT, # optional
issuer: "MyAPp",
ttl: { 30, :days },
verify_issuer: true, # optional
secret_key: "guardian_sekret",
serializer: Sentinel.GuardianSerializer,
hooks: GuardianDb,
permissions: Application.get_env(:sentinel, :permissions)

More info

Configure GuardianDb

config :guardian_db, GuardianDb,
repo: MyApp.Repo

The database backing for your tokens:

defmodule MyApp.Repo.Migrations.GuardianDb do
use Ecto.Migration
def up do
create table(:guardian_tokens, primary_key: false) do
add :jti, :string, primary_key: true
add :typ, :string
add :aud, :string
add :iss, :string
add :sub, :string
add :exp, :bigint
add :jwt, :text
add :claims, :map
timestamps
end
end
def down do
drop table(:guardian_tokens)
end
end

More info

Configure Sentinel

config :sentinel,
app_name: "Test App",
user_model: MyApp.User,
email_sender: "test@example.com",
crypto_provider: Comeonin.Bcrypt,
auth_handler: Sentinel.AuthHandler, #optional
repo: MyApp.Repo,
confirmable: :required, # possible options {:false, :required, :optional},optional config
endpoint: MyApp.Endpoint,
router: MyApp.Router

Configure Mailman

# Local server example
config :mailman
port: 1234
# Mailgun Example
config :mailman
port: 587,
address: "smtp.mailgun.org",
user_name: System.get_env("MAILGUN_USERNAME"),
password: System.get_env("MAILGUN_PASSWORD")
# Mandrill Example
config :mailman
port: 587,
address: "smtp.mandrillapp.com",
user_name: System.get_env("MANDRILL_USERNAME"),
password: System.get_env("MANDRILL_PASSWORD")

More info

Routes

Add the following to your routes file to add default routes, complete with protection

defmodule MyApp.Router do
use Phoenix.Router
require Sentinel
scope "/api" do
pipe_through :api
Sentinel.mount
end
scope "/api" do
pipe_through :authenticated
pipe_through :api
resources "/messages", MessagesController
end
end

The generated routes are:

methodpathdescription
POST/api/usersregister
POST/api/users/:id/confirmconfirm account
POST/api/sessionlogin, will return a token as JSON
DELETE/api/sessionlogout, invalidated the users current authentication token
POST/api/password_resetsrequest a reset-password-email
POST/api/password_resets/resetreset a password
GET/api/accountget information about the current user. at the moment this includes only the email address
PUT/api/accountupdate the current users email or password

Overriding the Defaults

Confirmable

By default users are not required to confirm their account to login. If you'd like to require confirmation set the confirmable configuration field to :required. If you don't want confirmation emails sent, set the field to :false. The default is :optional.

Custom Routes

If you want to customize the routes, or use your own controller endpoints you can do that by overriding the individual routes shown below:

post "users", Sentinel.Controllers.Users, :create
post "users/:id/confirm", Sentinel.Controllers.Users, :confirm
post "sessions", Sentinel.Controllers.Sessions, :create
delete "sessions", Sentinel.Controllers.Sessions, :delete
post "password_resets", Sentinel.Controllers.PasswordResets, :create
post "password_resets/reset", Sentinel.Controllers.PasswordResets, :reset
get "account", Sentinel.Controllers.Account, :show
put "account", Sentinel.Controllers.Account, :update

Mailer Customization/I18n

You setup your own email templates to send out by configuring the mailmanhtml_email_tempaltes and text_email_templates config variables to point to your own email template directories.

Internationalization is easy. Just make a folder for each langauge under your email template directory. Pass in the language to your mailer config

Auth Error Handler

If you'd like to write your own custom authorization or authentication handler change the auth_handler Sentinel configuration option to the module name of your handler.

It must define two functions, unauthorized/2, and unauthenticated/2, where the first parameter is the connection, and the second is information about the session.

Contributing/Want something new?

Create an issue. Preferably with a PR. If you're super awesome include tests.

As you recall from the license, this is provided as is. I don't make any money on this, so I do support when I feel like it. That said, I want to do my best to contribute to the Elixir/Phoenix community, so I'll do what I can.

Having said that if you bother to put up a PR I'll take a look, and either merge it, or let you know what needs to change before I do. Having experienced sending in PRs and never hearing anything about them, I know it sucks.

TODO

Ueberauth integration

Add a mix tasks that does all the configuration? and one that includes the user model and router stuff for a fresh, fresh project.

Status 201 instead of 200 in created?

Username based confirmation issue?

Mailer subject I18n

Still on the Roadmap

Session/View vs JWT/API configuration

Multiple Model types (user, admin)

Devise lockable equivalent

Devise trackable equivalent

Devise timeout-able equivalent