Packages

User accounts for Phoenix. Supports OAuth, JWT and forms out of the box

Current section

Files

Jump to
hr lib hr form_controller.ex
Raw

lib/hr/form_controller.ex

defmodule Hr.BaseFormController do
@moduledoc """
Base form controller. Can be "inherited" from by using `use Hr.BaseFormController`.
A good example of this is in `Hr.FormController`
"""
defmacro __using__(_) do
quote do
use Phoenix.Controller
@doc """
Display the login view and post the form to the next function
"""
def new_session(conn, _) do
{entity, model, repo, app} = Hr.Meta.stuff(conn)
# BAM!
path = apply(Module.concat(app, Router.Helpers), :"#{entity}_session_path", [Module.concat(app, Endpoint), :create_session])
conn
|> put_layout({Module.concat(app, LayoutView), :app})
|> put_view(Hr.Meta.form_view(app))
|> render("session.html", path: path)
end
@doc """
Check if the submitted credentials are valid, if they are, create a session
"""
def create_session(conn, %{"session" => %{"email" => email, "password" => password}}) do
{entity, model, repo, app} = Hr.Meta.stuff conn
path = apply(Module.concat(app, Router.Helpers), :"#{entity}_session_path", [Module.concat(app, Endpoint), :create_session])
case Hr.UserHelper.authenticate_with_email_and_password(model, repo, email, password) do
{:ok, user} ->
conn
|> Hr.Session.login(entity, user)
|> put_flash(:info, Hr.Meta.i18n(app, "sessions.signed_in"))
|> redirect(to: Hr.Meta.logged_in_url)
{:error, _reason} ->
conn
|> put_flash(:error, Hr.Meta.i18n(app, "sessions.invalid"))
|> put_layout({Module.concat(app, LayoutView), :app})
|> put_view(Hr.Meta.form_view(app))
|> render("session.html", path: path)
end
end
@doc """
Destroy the session
"""
def destroy_session(conn, _) do
{entity, model, repo, app} = Hr.Meta.stuff conn
conn
|> Hr.Session.logout
|> put_flash(:info, Hr.Meta.i18n(app, "sessions.signed_out"))
|> redirect(to: Hr.Meta.logged_out_url)
end
@doc """
redirect the user to the oauth provider's site so they can authenticate
"""
def oauth_authorize(conn, %{"provider" => provider}) do
{entity, model, repo, app} = Hr.Meta.stuff conn
case Hr.Meta.valid_oauth_provider(provider) do
true ->
url = Hr.OAuth.Strategies.find(provider).authorize_url!(entity)
redirect(conn, external: url)
_ ->
# error
end
end
@doc """
Try to look a user up by identity, if none exists, create one and log them in.
"""
def oauth_callback(conn, %{"provider" => provider, "code" => code}) do
{entity, model, repo, app} = Hr.Meta.stuff conn
case Hr.Meta.valid_oauth_provider(provider) do
true ->
strategy = Hr.OAuth.Strategies.find(provider)
identity = strategy.get_identity!(strategy.get_token!(entity, code: code))
identity_model = :"#{model}Identity"
# not a good idea to access the app inside the strategy anymore
identity = struct(identity_model, identity)
# if this OAuth id has a user associated with them,
# log them in, else create one
case Hr.UserHelper.authenticate_with_identity(repo, identity_model, entity, identity) do
{:ok, user} ->
conn
|> Hr.Session.login(entity, user)
|> put_flash(:info, Hr.Meta.i18n(app, "sessions.signed_in"))
|> redirect(to: Hr.Meta.logged_in_url)
{:error, _} ->
user = Hr.UserHelper.create_with_identity(repo, identity_model, model, entity, identity)
conn
|> Hr.Session.login(entity, user)
|> put_flash(:info, Hr.Meta.i18n(app, "registrations.signed_up"))
|> redirect(to: Hr.Meta.logged_in_url)
end
_ ->
# error
end
end
@doc """
Display the signup form and post to the next function
"""
def new_signup(conn, _) do
{entity, model, repo, app} = Hr.Meta.stuff conn
path = apply(Module.concat(app, Router.Helpers), :"#{entity}_signup_path", [Module.concat(app, Endpoint), :create_signup])
conn
|> put_layout({Module.concat(app, LayoutView), :app})
|> put_view(Hr.Meta.form_view(app))
|> render("signup.html", changeset: model.changeset(model.__struct__), path: path)
end
@doc """
Create a new user if the email address isn't already taken.
"""
def create_signup(conn, data) do
{entity, model, repo, app} = Hr.Meta.stuff conn
path = apply(Module.concat(app, Router.Helpers), :"#{entity}_signup_path", [Module.concat(app, Endpoint), :create_signup])
params = data[entity]
confirmable? = Enum.member? model.hr_behaviours, :confirmable
if confirmable? do
{changeset, token} = Hr.Model.confirmable_signup_changeset(model.__struct__, params)
else
changeset = Hr.Model.signup_changeset(model.__struct__, params)
end
case repo.insert(changeset) do
{:ok, user} ->
if confirmable? do
link = Hr.Meta.confirmation_url(conn, user.id, token)
Hr.Meta.mailer(app).send_confirmation_email(user, link)
conn
|> put_flash(:info, Hr.Meta.i18n(app, "registrations.signed_up_but_unconfirmed", email: user.unconfirmed_email))
|> redirect(to: Hr.Meta.signed_up_url)
else
conn
|> Hr.Session.login(entity, user)
|> put_flash(:info, Hr.Meta.i18n(app, "sessions.signed_in"))
|> redirect(to: Hr.Meta.logged_in_url)
end
{:error, changeset} ->
conn
|> put_layout({Module.concat(app, LayoutView), :app})
|> put_view(Hr.Meta.form_view(app))
|> render("signup.html", changeset: changeset, path: path)
end
end
@doc """
Confirms the email for the user
"""
def confirmation(conn, params = %{"id" => user_id, "confirmation_token" => _}) do
{entity, model, repo, app} = Hr.Meta.stuff conn
user = repo.get! model, user_id
changeset = Hr.Model.confirmation_changeset(user, params)
if changeset && changeset.valid? do
repo.update!(changeset)
conn
|> put_flash(:info, Hr.Meta.i18n(app, "confirmations.confirmed"))
|> redirect(to: Hr.Meta.signed_up_url)
else
conn
|> put_flash(:error, Hr.Meta.i18n(app, "confirmations.invalid_confirmation_token"))
|> redirect(to: Hr.Meta.signed_up_url)
end
end
@doc """
Captures the email address to send a password reset link to
"""
def new_password_reset_request(conn, _) do
{entity, model, repo, app} = Hr.Meta.stuff conn
path = apply(Module.concat(app, Router.Helpers), :"#{entity}_password_reset_request_path", [Module.concat(app, Endpoint), :create_password_reset_request])
conn
|> put_layout({Module.concat(app, LayoutView), :app})
|> put_view(Hr.Meta.form_view(app))
|> render("password_reset_request.html", path: path)
end
@doc """
If the email exists, send a reset link
"""
def create_password_reset_request(conn, %{"reset" => %{"email" => email}}) do
{entity, model, repo, app} = Hr.Meta.stuff conn
user = repo.get_by! model, email: email
case user do
nil ->
nil
user ->
{changeset, token} = Hr.Model.reset_changeset(user)
repo.update!(changeset)
link = Hr.Meta.reset_url(conn, user.id, token)
Hr.Meta.mailer(app).send_reset_email(user, link)
end
conn
|> put_flash(:info, Hr.Meta.i18n(app, "passwords.send_instructions"))
|> redirect(to: Hr.Meta.signed_up_url)
end
@doc """
capture the requested new password from the user
"""
def new_password_reset(conn, %{"id" => id, "password_reset_token" => token}) do
{entity, model, repo, app} = Hr.Meta.stuff conn
path = apply(Module.concat(app, Router.Helpers), :"#{entity}_password_reset_path", [Module.concat(app, Endpoint), :new_password_reset])
conn
|> put_layout({Module.concat(app, LayoutView), :app})
|> put_view(Hr.Meta.form_view(app))
|> render("password_reset.html", id: id, token: token, path: path)
end
@doc """
Reset the user's password
"""
def create_password_reset(conn, %{"reset" => params}) do
{entity, model, repo, app} = Hr.Meta.stuff conn
path = apply(Module.concat(app, Router.Helpers), :"#{entity}_password_reset_path", [Module.concat(app, Endpoint), :create_password_reset])
case Hr.UserHelper.get_with_id_and_token(repo, model, params["id"], params["password_reset_token"]) do
{:ok, user} ->
changeset = Hr.Model.new_password_changeset(user, params)
if changeset.valid? do
repo.update!(changeset)
conn
|> put_flash(:info, Hr.Meta.i18n(app, "passwords.updated"))
|> redirect(to: Hr.Meta.signed_up_url)
else
conn
|> put_layout({Module.concat(app, LayoutView), :app})
|> put_flash(:error, Hr.Meta.i18n(app, "passwords.invalid"))
|> put_view(Hr.Meta.form_view(app))
|> render("password_reset.html", changeset: changeset, id: params["id"], token: params["password_reset_token"], path: path)
end
{:error, _} ->
conn
|> put_flash(:error, Hr.Meta.i18n(app, "passwords.no_token"))
|> redirect(to: Hr.Meta.signed_up_url)
end
end
end
end
end
defmodule Hr.FormController do
@moduledoc """
The default implementation of FormController. If you want to override
it, either direct your routes to your own implementation (if you want to
just override a couple of actions), or implement `YourApp.HrFormController`
with `use Hr.BaseFormController`.
"""
use Hr.BaseFormController
end