这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 0 additions & 40 deletions lib/authentication/authentication.ex

This file was deleted.

7 changes: 0 additions & 7 deletions lib/authentication/store.ex

This file was deleted.

83 changes: 0 additions & 83 deletions lib/authentication/stores/ecto_store.ex

This file was deleted.

57 changes: 57 additions & 0 deletions lib/authority/authentication.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
defmodule Authority.Authentication do
@type id :: any
@type credential :: {id, any} | any
@type identity :: any
@type purpose :: atom
@type error :: {:error, term}

@callback authenticate(credential, purpose) :: :ok | error

@callback before_identify(id) :: {:ok, id} | error
@callback identify(id) :: {:ok, identity} | error
@callback before_validate(identity, purpose) :: :ok | error
@callback validate(credential, identity, purpose) :: :ok | error
@callback after_validate(identity, purpose) :: :ok | error
@callback failed(identity, error) :: :ok | error

defmacro __using__(_) do
quote do
@behaviour Authority.Authentication

def authenticate(credential, purpose \\ :any) do
Authority.Authentication.authenticate(__MODULE__, credential, purpose)
end

def before_identify(identifier), do: {:ok, identifier}
def before_validate(_identity, _purpose), do: :ok
def after_validate(_identity, _purpose), do: :ok
def failed(_identity, _error), do: :ok

defoverridable Authority.Authentication
end
end

@doc false
def authenticate(module, {identifier, credential}, purpose) do
do_authenticate(module, identifier, credential, purpose)
end

def authenticate(module, credential, purpose) do
do_authenticate(module, credential, credential, purpose)
end

defp do_authenticate(module, identifier, credential, purpose) do
with {:ok, identifier} <- module.before_identify(identifier),
{:ok, identity} <- module.identify(identifier) do
with :ok <- module.before_validate(identity, purpose),
:ok <- module.validate(credential, identity, purpose),
:ok <- module.after_validate(identity, purpose) do
{:ok, identity}
else
error ->
module.failed(identity, error)
error
end
end
end
end
38 changes: 38 additions & 0 deletions lib/authority/template.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule Authority.Template do
alias Authority.{
Authentication,
Tokenization,
Template
}

@templates %{
[Authentication] => Template.Authenticate,
[Authentication, Tokenization] => Template.AuthenticateTokenize
}

defmodule Error do
defexception [:message]
end

defmacro __using__(config) do
{config, _} = Code.eval_quoted(config, [], __CALLER__)

unless config[:behaviours] do
raise Error, "You must specify :behaviours"
end

unless config[:config] do
raise Error, "You must specify :config"
end

template = @templates[Enum.sort(config[:behaviours])]

unless template do
raise Error, "No template found for behaviours #{inspect(config[:behaviours])}"
end

quote do
use unquote(template), unquote(config[:config])
end
end
end
37 changes: 37 additions & 0 deletions lib/authority/templates/authenticate.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule Authority.Template.Authenticate do
defmacro __using__(config) do
quote do
@config unquote(config)
@repo @config[:repo]

@user_module @config[:user_module]
@user_identity_field @config[:user_identity_field]
@user_password_field @config[:user_password_field]
@user_password_algorithm @config[:user_password_algorithm] || :bcrypt

use Authority.Authentication

def identify(identifier) do
case @repo.get_by(@user_module, [{@user_identity_field, identifier}]) do
nil -> {:error, :"invalid_#{@user_identity_field}"}
user -> {:ok, user}
end
end

if @user_password_algorithm == :bcrypt do
def validate(
password,
%@user_module{@user_password_field => encrypted_password},
_purpose
) do
case Comeonin.Bcrypt.checkpw(password, encrypted_password) do
true -> :ok
false -> {:error, :invalid_password}
end
end
end

defoverridable Authority.Authentication
end
end
end
108 changes: 108 additions & 0 deletions lib/authority/templates/authenticate_tokenize.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
defmodule Authority.Template.AuthenticateTokenize do
defmacro __using__(config) do
quote do
@config unquote(config)
@repo @config[:repo]

@token_module @config[:token_module]
@token_field @config[:token_field] || :token
@token_user_assoc @config[:token_user_assoc] || :user
@token_expiration_field @config[:token_expiration_field] || :expires_at
@token_purpose_field @config[:token_purpose_field] || :purpose

@user_module @config[:user_module]
@user_identity_field @config[:user_identity_field] || :email
@user_password_field @config[:user_password_field] || :encrypted_password
@user_password_algorithm @config[:user_password_algorithm] || :bcrypt

# AUTHENTICATION
# —————————————————————————————————————————————————————————————————————————

use Authority.Authentication

# Refresh the token from the `token` attribute, so that you
# don't have to pass the full token
def before_identify(%@token_module{@token_field => value} = token) do
token =
@token_module
|> @repo.get_by([{@token_field, value}])
|> @repo.preload(@token_user_assoc)

case token do
nil -> {:error, :invalid_token}
token -> {:ok, token}
end
end

def before_identify(identifier), do: {:ok, identifier}

def identify(%@token_module{@token_user_assoc => %@user_module{} = user}) do
{:ok, user}
end

def identify(identifier) do
case @repo.get_by(@user_module, [{@user_identity_field, identifier}]) do
nil -> {:error, :"invalid_#{@user_identity_field}"}
user -> {:ok, user}
end
end

def validate(%@token_module{@token_purpose_field => token_purpose} = token, _user, purpose)
when token_purpose == :any or token_purpose == purpose do
if DateTime.compare(DateTime.utc_now(), token[@token_expiration_field]) == :lt do
:ok
else
{:error, :expired_token}
end
end

def validate(%@token_module{}, _user, _purpose) do
{:error, :invalid_token_for_purpose}
end

if @user_password_algorithm == :bcrypt do
def validate(
password,
%@user_module{@user_password_field => encrypted_password},
_purpose
) do
case Comeonin.Bcrypt.checkpw(password, encrypted_password) do
true -> :ok
false -> {:error, :invalid_password}
end
end
end

defoverridable Authority.Authentication

# TOKENIZATION
# —————————————————————————————————————————————————————————————————————————

use Authority.Tokenization

def tokenize({identifier, password}, purpose) do
with {:ok, user} <- authenticate({identifier, password}, purpose) do
do_tokenize(user, purpose)
end
end

def tokenize(identifier, :recovery) do
with {:ok, user} <- identify(identifier) do
do_tokenize(user, purpose)
end
end

def tokenize(_other, _purpose) do
{:error, :invalid_credential_for_purpose}
end

defp do_tokenize(user, purpose) do
%@token_module{@token_user_assoc => user}
|> @token_module.insert_changeset(%{@token_purpose_field => purpose})
|> @repo.insert()
end

defoverridable Authority.Tokenization
end
end
end
12 changes: 12 additions & 0 deletions lib/authority/tokenization.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule Authority.Tokenization do
@type token :: any
@type identity :: any
@type purpose :: atom
@callback tokenize(identity, purpose) :: {:ok, token} | {:error, term}

defmacro __using__(_) do
quote do
@behaviour Authority.Tokenization
end
end
end
Loading