Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add Token Based User Auth using Coherence #1

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions config/config.exs
Expand Up @@ -25,3 +25,16 @@ config :logger, :console,
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"

# %% Coherence Configuration %% Don't remove this line
config :coherence,
user_schema: UserAuth.Coherence.User,
repo: UserAuth.Repo,
module: UserAuth,
web_module: UserAuthWeb,
router: UserAuthWeb.Router,
messages_backend: UserAuthWeb.Coherence.Messages,
logged_out_url: "/",
opts: [:authenticatable],
user_token: true
# %% End Coherence Configuration %%
141 changes: 141 additions & 0 deletions lib/user_auth/coherence/schemas.ex
@@ -0,0 +1,141 @@
defmodule UserAuth.Coherence.Schemas do

use Coherence.Config

import Ecto.Query

@user_schema Config.user_schema
@repo Config.repo

def list_user do
@repo.all @user_schema
end

def get_by_user(opts) do
@repo.get_by @user_schema, opts
end

def get_user(id) do
@repo.get @user_schema, id
end

def get_user!(id) do
@repo.get! @user_schema, id
end

def get_user_by_email(email) do
@repo.get_by @user_schema, email: email
end

def change_user(struct, params) do
@user_schema.changeset struct, params
end

def change_user(params) do
@user_schema.changeset @user_schema.__struct__, params
end

def change_user do
@user_schema.changeset @user_schema.__struct__, %{}
end

def update_user(user, params) do
@repo.update change_user(user, params)
end

def create_user(params) do
@repo.insert change_user(params)
end

Enum.each [], fn module ->

name =
module
|> Module.split
|> List.last
|> String.downcase

def unquote(String.to_atom("list_#{name}"))() do
@repo.all unquote(module)
end

def unquote(String.to_atom("list_#{name}"))(%Ecto.Query{} = query) do
@repo.all query
end

def unquote(String.to_atom("get_#{name}"))(id) do
@repo.get unquote(module), id
end

def unquote(String.to_atom("get_#{name}!"))(id) do
@repo.get! unquote(module), id
end

def unquote(String.to_atom("get_by_#{name}"))(opts) do
@repo.get_by unquote(module), opts
end

def unquote(String.to_atom("change_#{name}"))(struct, params) do
unquote(module).changeset(struct, params)
end

def unquote(String.to_atom("change_#{name}"))(params) do
unquote(module).new_changeset(params)
end

def unquote(String.to_atom("change_#{name}"))() do
unquote(module).new_changeset(%{})
end

def unquote(String.to_atom("create_#{name}"))(params) do
@repo.insert unquote(module).new_changeset(params)
end

def unquote(String.to_atom("update_#{name}"))(struct, params) do
@repo.update unquote(module).changeset(struct, params)
end

def unquote(String.to_atom("delete_#{name}"))(struct) do
@repo.delete struct
end
end

def query_by(schema, opts) do
Enum.reduce opts, schema, fn {k, v}, query ->
where(query, [b], field(b, ^k) == ^v)
end
end

def delete_all(%Ecto.Query{} = query) do
@repo.delete_all query
end

def delete_all(module) when is_atom(module) do
@repo.delete_all module
end

def create(%Ecto.Changeset{} = changeset) do
@repo.insert changeset
end

def create!(%Ecto.Changeset{} = changeset) do
@repo.insert! changeset
end

def update(%Ecto.Changeset{} = changeset) do
@repo.update changeset
end

def update!(%Ecto.Changeset{} = changeset) do
@repo.update! changeset
end

def delete(schema) do
@repo.delete schema
end

def delete!(schema) do
@repo.delete! schema
end

end
30 changes: 30 additions & 0 deletions lib/user_auth/coherence/user.ex
@@ -0,0 +1,30 @@
defmodule UserAuth.Coherence.User do
@moduledoc false
use Ecto.Schema
use Coherence.Schema



schema "users" do
field :name, :string
field :email, :string
coherence_schema()

timestamps()
end

def changeset(model, params \\ %{}) do
model
|> cast(params, [:name, :email] ++ coherence_fields())
|> validate_required([:name, :email])
|> validate_format(:email, ~r/@/)
|> unique_constraint(:email)
|> validate_coherence(params)
end

def changeset(model, params, :password) do
model
|> cast(params, ~w(password password_confirmation reset_password_token reset_password_sent_at))
|> validate_coherence_password_reset(params)
end
end
5 changes: 4 additions & 1 deletion lib/user_auth_web/channels/user_socket.ex
Expand Up @@ -20,7 +20,10 @@ defmodule UserAuthWeb.UserSocket do
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
def connect(_params, socket) do
{:ok, socket}
case Coherence.verify_user_token(socket, token, &assign/3) do
{:error, _} -> :error
{:ok, socket} -> {:ok, socket}
end
end

# Socket id's are topics that allow you to identify all sockets for a given user:
Expand Down
79 changes: 79 additions & 0 deletions lib/user_auth_web/coherence_messages.ex
@@ -0,0 +1,79 @@
defmodule UserAuthWeb.Coherence.Messages do
@moduledoc """
Application facing messages generated by the Coherence application.

This module was created by the coh.install mix task. It contains all the
messages used in the coherence application except those in other generated
files like the view and templates.

To assist in upgrading Coherence, the `Coherence.Messages behaviour will
alway contain every message for the current version. This will help in upgrades
to ensure the user had added new the new messages from the current version.
"""
@behaviour Coherence.Messages

import UserAuthWeb.Gettext

# Change this to override the "coherence" gettext domain. If you would like
# the coherence message to be part of your projects domain change it to "default"
@domain "coherence"

##################
# Messages

def account_already_confirmed, do: dgettext(@domain, "Account already confirmed.")
def account_is_not_locked, do: dgettext(@domain, "Account is not locked.")
def account_updated_successfully, do: dgettext(@domain, "Account updated successfully.")
def already_confirmed, do: dgettext(@domain, "already confirmed")
def already_locked, do: dgettext(@domain, "already locked")
def already_logged_in, do: dgettext(@domain, "Already logged in.")
def cant_be_blank, do: dgettext(@domain, "can't be blank")
def cant_find_that_token, do: dgettext(@domain, "Can't find that token")
def confirmation_email_sent, do: dgettext(@domain, "Confirmation email sent.")
def confirmation_token_expired, do: dgettext(@domain, "Confirmation token expired.")
def could_not_find_that_email_address, do: dgettext(@domain, "Could not find that email address")
def forgot_your_password, do: dgettext(@domain, "Forgot your password?")
def http_authentication_required, do: dgettext(@domain, "HTTP Authentication Required")
def incorrect_login_or_password(opts), do: dgettext(@domain, "Incorrect %{login_field} or password.", opts)
def invalid_current_password, do: dgettext(@domain, "invalid current password")
def invalid_invitation, do: dgettext(@domain, "Invalid Invitation. Please contact the site administrator.")
def invalid_request, do: dgettext(@domain, "Invalid Request.")
def invalid_confirmation_token, do: dgettext(@domain, "Invalid confirmation token.")
def invalid_email_or_password, do: dgettext(@domain, "Invalid email or password.")
def invalid_invitation_token, do: dgettext(@domain, "Invalid invitation token.")
def invalid_reset_token, do: dgettext(@domain, "Invalid reset token.")
def invalid_unlock_token, do: dgettext(@domain, "Invalid unlock token.")
def invitation_already_sent, do: dgettext(@domain, "Invitation already sent.")
def invitation_sent, do: dgettext(@domain, "Invitation sent.")
def invite_someone, do: dgettext(@domain, "Invite Someone")
def maximum_login_attempts_exceeded, do: dgettext(@domain, "Maximum Login attempts exceeded. Your account has been locked.")
def need_an_account, do: dgettext(@domain, "Need An Account?")
def not_locked, do: dgettext(@domain, "not locked")
def password_reset_token_expired, do: dgettext(@domain, "Password reset token expired.")
def password_updated_successfully, do: dgettext(@domain, "Password updated successfully.")
def problem_confirming_user_account, do: dgettext(@domain, "Problem confirming user account. Please contact the system administrator.")
def registration_created_successfully, do: dgettext(@domain, "Registration created successfully.")
def required, do: dgettext(@domain, "required")
def resend_confirmation_email, do: dgettext(@domain, "Resend confirmation email")
def reset_email_sent, do: dgettext(@domain, "Reset email sent. Check your email for a reset link.")
def restricted_area, do: dgettext(@domain, "Restricted Area")
def send_an_unlock_email, do: dgettext(@domain, "Send an unlock email")
def sign_in, do: dgettext(@domain, "Sign In")
def sign_out, do: dgettext(@domain, "Sign Out")
def signed_in_successfully, do: dgettext(@domain, "Signed in successfully.")
def too_many_failed_login_attempts, do: dgettext(@domain, "Too many failed login attempts. Account has been locked.")
def unauthorized_ip_address, do: dgettext(@domain, "Unauthorized IP Address")
def unlock_instructions_sent, do: dgettext(@domain, "Unlock Instructions sent.")
def user_account_confirmed_successfully, do: dgettext(@domain, "User account confirmed successfully.")
def user_already_has_an_account, do: dgettext(@domain, "User already has an account!")
def you_must_confirm_your_account, do: dgettext(@domain, "You must confirm your account before you can login.")
def your_account_has_been_unlocked, do: dgettext(@domain, "Your account has been unlocked")
def your_account_is_not_locked, do: dgettext(@domain, "Your account is not locked.")
def verify_user_token(opts),
do: dgettext(@domain, "Invalid %{user_token} error: %{error}", opts)
def you_are_using_an_invalid_security_token,
do: dgettext(@domain, "You are using an invalid security token for this site! This security\n" <>
"violation has been logged.\n")
def mailer_required, do: dgettext(@domain, "Mailer configuration required!")
def account_is_inactive(), do: dgettext(@domain, "Account is inactive!")
end
47 changes: 47 additions & 0 deletions lib/user_auth_web/coherence_web.ex
@@ -0,0 +1,47 @@
defmodule UserAuthWeb.Coherence do
@moduledoc false

def view do
quote do
use Phoenix.View, root: "lib/user_auth_web/templates"
# Import convenience functions from controllers

import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]

# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML

import UserAuthWeb.Router.Helpers
import UserAuthWeb.ErrorHelpers
import UserAuthWeb.Gettext
import UserAuthWeb.Coherence.ViewHelpers
end
end

def controller do
quote do
use Phoenix.Controller, except: [layout_view: 2]
use Coherence.Config
use Timex

import Ecto
import Ecto.Query
import Plug.Conn
import UserAuthWeb.Router.Helpers
import UserAuthWeb.Gettext
import Coherence.ControllerHelpers

alias Coherence.Config
alias Coherence.ControllerHelpers, as: Helpers

require Redirects
end
end

@doc """
When used, dispatch to the appropriate controller/view/etc.
"""
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
end
54 changes: 54 additions & 0 deletions lib/user_auth_web/controllers/coherence/redirects.ex
@@ -0,0 +1,54 @@
defmodule Coherence.Redirects do
@moduledoc """
Define controller action redirection functions.

This module contains default redirect functions for each of the controller
actions that perform redirects. By using this Module you get the following
functions:

* session_create/2
* session_delete/2
* password_create/2
* password_update/2,
* unlock_create_not_locked/2
* unlock_create_invalid/2
* unlock_create/2
* unlock_edit_not_locked/2
* unlock_edit/2
* unlock_edit_invalid/2
* registration_create/2
* invitation_create/2
* confirmation_create/2
* confirmation_edit_invalid/2
* confirmation_edit_expired/2
* confirmation_edit/2
* confirmation_edit_error/2

You can override any of the functions to customize the redirect path. Each
function is passed the `conn` and `params` arguments from the controller.

## Examples

import UserAuthWeb.Router.Helpers

# override the log out action back to the log in page
def session_delete(conn, _), do: redirect(conn, to: session_path(conn, :new))

# redirect the user to the login page after registering
def registration_create(conn, _), do: redirect(conn, to: session_path(conn, :new))

# disable the user_return_to feature on login
def session_create(conn, _), do: redirect(conn, to: landing_path(conn, :index))

"""
use Redirects
# Uncomment the import below if adding overrides
# import UserAuthWeb.Router.Helpers

# Add function overrides below

# Example usage
# Uncomment the following line to return the user to the login form after logging out
# def session_delete(conn, _), do: redirect(conn, to: session_path(conn, :new))

end