mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 20:18:00 +01:00
add bulk of totp logic
This commit is contained in:
parent
0f6e94d1a9
commit
f1726e3d52
14 changed files with 250 additions and 138 deletions
|
@ -51,7 +51,7 @@ defmodule Philomena.Images.Query do
|
||||||
must_not
|
must_not
|
||||||
end
|
end
|
||||||
|
|
||||||
%{bool: %{should: should, must_not: must_not}}
|
{:ok, %{bool: %{should: should, must_not: must_not}}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_my_transform(_ctx, _value),
|
def user_my_transform(_ctx, _value),
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
defmodule Philomena.Users.Password do
|
defmodule Philomena.Users.Password do
|
||||||
def hash_pwd_salt(password, opts \\ []) do
|
def hash_pwd_salt(password, opts \\ []) do
|
||||||
pepper = Application.get_env(:philomena, :password_pepper)
|
Bcrypt.hash_pwd_salt(<<password::binary, password_pepper()::binary>>, opts)
|
||||||
|
|
||||||
Bcrypt.hash_pwd_salt(<<password::binary, pepper::binary>>, opts)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_pass(password, stored_hash) do
|
def verify_pass(password, stored_hash) do
|
||||||
pepper = Application.get_env(:philomena, :password_pepper)
|
Bcrypt.verify_pass(<<password::binary, password_pepper()::binary>>, stored_hash)
|
||||||
|
end
|
||||||
|
|
||||||
Bcrypt.verify_pass(<<password::binary, pepper::binary>>, stored_hash)
|
defp password_pepper do
|
||||||
|
Application.get_env(:philomena, :password_pepper)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Philomena.Users.User do
|
||||||
password_hash_methods: {&Password.hash_pwd_salt/1, &Password.verify_pass/2}
|
password_hash_methods: {&Password.hash_pwd_salt/1, &Password.verify_pass/2}
|
||||||
|
|
||||||
use Pow.Extension.Ecto.Schema,
|
use Pow.Extension.Ecto.Schema,
|
||||||
extensions: [PowResetPassword]
|
extensions: [PowResetPassword, PowPersistentSession]
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ defmodule Philomena.Users.User do
|
||||||
has_many :links, Philomena.Users.Link
|
has_many :links, Philomena.Users.Link
|
||||||
has_many :verified_links, Philomena.Users.Link, where: [aasm_state: "verified"]
|
has_many :verified_links, Philomena.Users.Link, where: [aasm_state: "verified"]
|
||||||
has_many :public_links, Philomena.Users.Link, where: [public: true, aasm_state: "verified"]
|
has_many :public_links, Philomena.Users.Link, where: [public: true, aasm_state: "verified"]
|
||||||
has_many :galleries, Philomena.Galleries.Gallery
|
has_many :galleries, Philomena.Galleries.Gallery, foreign_key: :creator_id
|
||||||
has_many :awards, Philomena.Badges.Award
|
has_many :awards, Philomena.Badges.Award
|
||||||
|
|
||||||
belongs_to :current_filter, Philomena.Filters.Filter
|
belongs_to :current_filter, Philomena.Filters.Filter
|
||||||
|
@ -116,24 +116,97 @@ defmodule Philomena.Users.User do
|
||||||
|> validate_required([])
|
|> validate_required([])
|
||||||
end
|
end
|
||||||
|
|
||||||
def otp_secret(%{encrypted_otp_secret: x} = user) when x not in [nil, ""] do
|
def create_totp_secret_changeset(user) do
|
||||||
Philomena.Users.Encryptor.decrypt_model(
|
secret = :crypto.strong_rand_bytes(15) |> Base.encode32()
|
||||||
user.encrypted_otp_secret,
|
|
||||||
user.encrypted_otp_secret_iv,
|
|
||||||
user.encrypted_otp_secret_salt
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def otp_secret(_user), do: nil
|
|
||||||
|
|
||||||
def put_otp_secret(user_or_changeset, secret) do
|
|
||||||
data = Philomena.Users.Encryptor.encrypt_model(secret)
|
data = Philomena.Users.Encryptor.encrypt_model(secret)
|
||||||
|
|
||||||
user_or_changeset
|
user
|
||||||
|> change(%{
|
|> change(%{
|
||||||
encrypted_otp_secret: data.secret,
|
encrypted_otp_secret: data.secret,
|
||||||
encrypted_otp_secret_iv: data.iv,
|
encrypted_otp_secret_iv: data.iv,
|
||||||
encrypted_otp_secret_salt: data.salt
|
encrypted_otp_secret_salt: data.salt
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def consume_totp_token_changeset(user, token) do
|
||||||
|
cond do
|
||||||
|
totp_valid?(user, token) ->
|
||||||
|
user
|
||||||
|
|> change(%{consumed_timestep: token})
|
||||||
|
|
||||||
|
backup_code_valid?(user, token) ->
|
||||||
|
user
|
||||||
|
|> change(%{otp_backup_codes: remove_backup_code(user, token)})
|
||||||
|
|
||||||
|
true ->
|
||||||
|
user
|
||||||
|
|> add_error(:consumed_timestep, "invalid token")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def totp_changeset(user, params, backup_codes) do
|
||||||
|
token = to_string(params["twofactor_token"])
|
||||||
|
|
||||||
|
case user.otp_required_for_login do
|
||||||
|
true ->
|
||||||
|
# User wants to disable TOTP
|
||||||
|
user
|
||||||
|
|> pow_current_password_changeset(params)
|
||||||
|
|> consume_totp_token_changeset(token)
|
||||||
|
|> disable_totp_changeset()
|
||||||
|
|
||||||
|
false ->
|
||||||
|
# User wants to enable TOTP
|
||||||
|
user
|
||||||
|
|> pow_current_password_changeset(params)
|
||||||
|
|> consume_totp_token_changeset(token)
|
||||||
|
|> enable_totp_changeset(backup_codes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def random_backup_codes do
|
||||||
|
(1..10)
|
||||||
|
|> Enum.map(fn _i ->
|
||||||
|
:crypto.strong_rand_bytes(6) |> Base.encode16(case: :lower)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
defp enable_totp_changeset(user, backup_codes) do
|
||||||
|
hashed_codes =
|
||||||
|
backup_codes
|
||||||
|
|> Enum.map(&Password.hash_pwd_salt/1)
|
||||||
|
|
||||||
|
user
|
||||||
|
|> change(%{
|
||||||
|
otp_required_for_login: true,
|
||||||
|
otp_backup_codes: hashed_codes
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp disable_totp_changeset(user) do
|
||||||
|
user
|
||||||
|
|> change(%{
|
||||||
|
otp_required_for_login: false,
|
||||||
|
otp_backup_codes: []
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp totp_valid?(user, token),
|
||||||
|
do: :pot.valid_totp(token, otp_secret(user), window: 60)
|
||||||
|
|
||||||
|
defp backup_code_valid?(user, token),
|
||||||
|
do: Enum.any?(user.otp_backup_codes, &Password.verify_pass(token, &1))
|
||||||
|
|
||||||
|
defp remove_backup_code(user, token),
|
||||||
|
do: user.otp_backup_codes |> Enum.reject(&Password.verify_pass(token, &1))
|
||||||
|
|
||||||
|
|
||||||
|
defp otp_secret(user) do
|
||||||
|
Philomena.Users.Encryptor.decrypt_model(
|
||||||
|
user.encrypted_otp_secret,
|
||||||
|
user.encrypted_otp_secret_iv,
|
||||||
|
user.encrypted_otp_secret_salt
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
defmodule PhilomenaWeb.CommentController do
|
defmodule PhilomenaWeb.CommentController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
alias Philomena.{Images.Image, Comments.Comment, Textile.Renderer}
|
alias Philomena.{Comments.Comment, Textile.Renderer}
|
||||||
alias Philomena.Repo
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
defmodule PhilomenaWeb.ImageController do
|
defmodule PhilomenaWeb.ImageController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
alias Philomena.{Images.Image, Comments.Comment, Tags.Tag, Textile.Renderer}
|
alias Philomena.{Images.Image, Comments.Comment, Textile.Renderer}
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
defmodule PhilomenaWeb.ProfileController do
|
defmodule PhilomenaWeb.ProfileController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
alias Philomena.{Images, Images.Image, Comments.Comment, Posts.Post, Users.User, Users.Link}
|
alias Philomena.{Images, Images.Image, Users.User}
|
||||||
alias Philomena.Repo
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
plug :load_and_authorize_resource, model: User, only: :show, id_field: "slug", preload: [awards: :badge, public_links: :tag]
|
plug :load_and_authorize_resource, model: User, only: :show, id_field: "slug", preload: [awards: :badge, public_links: :tag]
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule PhilomenaWeb.Registration.TotpController do
|
||||||
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Users.User
|
||||||
|
alias Philomena.Repo
|
||||||
|
|
||||||
|
def edit(conn, _params) do
|
||||||
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
|
case user.encrypted_otp_secret do
|
||||||
|
nil ->
|
||||||
|
user
|
||||||
|
|> User.create_totp_secret_changeset()
|
||||||
|
|> Repo.update()
|
||||||
|
|
||||||
|
# Redirect to have Pow pick up the changes
|
||||||
|
redirect(conn, to: Routes.registration_totp_path(conn, :edit))
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
changeset = Pow.Plug.change_user(conn)
|
||||||
|
render(conn, "edit.html", changeset: changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(conn, params) do
|
||||||
|
backup_codes = User.random_backup_codes()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> Pow.Plug.current_user()
|
||||||
|
|> User.totp_changeset(params, backup_codes)
|
||||||
|
|> Repo.update()
|
||||||
|
|> case do
|
||||||
|
{:error, changeset} ->
|
||||||
|
render(conn, "edit.html", changeset: changeset)
|
||||||
|
|
||||||
|
{:ok, user} ->
|
||||||
|
conn
|
||||||
|
|> PhilomenaWeb.Plugs.TotpPlug.update_valid_totp_at_for_session(user)
|
||||||
|
|> put_flash(:totp_backup_codes, backup_codes)
|
||||||
|
|> redirect(to: Routes.registration_totp_path(conn, :edit))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
31
lib/philomena_web/controllers/session/totp_controller.ex
Normal file
31
lib/philomena_web/controllers/session/totp_controller.ex
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
defmodule PhilomenaWeb.Session.TotpController do
|
||||||
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Users.User
|
||||||
|
alias Philomena.Repo
|
||||||
|
|
||||||
|
def new(conn, _params) do
|
||||||
|
changeset = Pow.Plug.change_user(conn)
|
||||||
|
|
||||||
|
render(conn, "new.html", changeset: changeset)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, params) do
|
||||||
|
conn
|
||||||
|
|> Pow.Plug.current_user()
|
||||||
|
|> User.consume_totp_token_changeset(params)
|
||||||
|
|> Repo.update()
|
||||||
|
|> case do
|
||||||
|
{:error, _changeset} ->
|
||||||
|
conn
|
||||||
|
|> Pow.Plug.clear_authenticated_user()
|
||||||
|
|> put_flash(:error, "Sorry, invalid TOTP token entered. Please sign in again.")
|
||||||
|
|> redirect(to: Routes.pow_session_path(conn, :new))
|
||||||
|
|
||||||
|
{:ok, user} ->
|
||||||
|
conn
|
||||||
|
|> PhilomenaWeb.Plugs.TotpPlug.update_valid_totp_at_for_session(user)
|
||||||
|
|> redirect(to: "/")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
58
lib/philomena_web/plugs/totp_plug.ex
Normal file
58
lib/philomena_web/plugs/totp_plug.ex
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
defmodule PhilomenaWeb.Plugs.TotpPlug do
|
||||||
|
@moduledoc """
|
||||||
|
This plug ensures that a user session has a valid TOTP.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
plug PhilomenaWeb.TotpPlug
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias PhilomenaWeb.Router.Helpers, as: Routes
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec init(any()) :: any()
|
||||||
|
def init(opts), do: opts
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec call(Plug.Conn.t(), any()) :: Plug.Conn.t()
|
||||||
|
def call(conn, _opts) do
|
||||||
|
conn
|
||||||
|
|> Pow.Plug.current_user()
|
||||||
|
|> case do
|
||||||
|
nil -> conn
|
||||||
|
user -> maybe_require_totp_phase(user, conn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_require_totp_phase(%{otp_required_for_login: nil}, conn), do: conn
|
||||||
|
defp maybe_require_totp_phase(%{otp_required_for_login: false}, conn), do: conn
|
||||||
|
defp maybe_require_totp_phase(_user, conn) do
|
||||||
|
conn.private
|
||||||
|
|> Map.get(:pow_session_metadata, [])
|
||||||
|
|> Keyword.get(:valid_totp_at)
|
||||||
|
|> case do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|> Phoenix.Controller.redirect(to: Routes.session_totp_path(conn, :new))
|
||||||
|
|> Plug.Conn.halt()
|
||||||
|
|
||||||
|
_valid_at ->
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec update_valid_totp_at_for_session(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
||||||
|
def update_valid_totp_at_for_session(conn, user) do
|
||||||
|
metadata =
|
||||||
|
conn.private
|
||||||
|
|> Map.get(:pow_session_metadata, [])
|
||||||
|
|> Keyword.put(:valid_totp_at, DateTime.utc_now())
|
||||||
|
|
||||||
|
config = Pow.Plug.fetch_config(conn)
|
||||||
|
plug = Pow.Plug.get_plug(config)
|
||||||
|
conn = Plug.Conn.put_private(conn, :pow_session_metadata, metadata)
|
||||||
|
|
||||||
|
plug.do_create(conn, user, config)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule PhilomenaWeb.Router do
|
defmodule PhilomenaWeb.Router do
|
||||||
use PhilomenaWeb, :router
|
use PhilomenaWeb, :router
|
||||||
use Pow.Phoenix.Router
|
use Pow.Phoenix.Router
|
||||||
|
use Pow.Extension.Phoenix.Router, otp_app: :philomena
|
||||||
|
|
||||||
pipeline :browser do
|
pipeline :browser do
|
||||||
plug :accepts, ["html"]
|
plug :accepts, ["html"]
|
||||||
|
@ -16,14 +17,28 @@ defmodule PhilomenaWeb.Router do
|
||||||
plug :accepts, ["json"]
|
plug :accepts, ["json"]
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/" do
|
pipeline :ensure_totp do
|
||||||
pipe_through :browser
|
plug PhilomenaWeb.Plugs.TotpPlug
|
||||||
|
end
|
||||||
|
|
||||||
#pow_routes()
|
scope "/" do
|
||||||
|
pipe_through [:browser, :ensure_totp]
|
||||||
|
|
||||||
|
pow_routes()
|
||||||
|
pow_extension_routes()
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", PhilomenaWeb do
|
scope "/", PhilomenaWeb do
|
||||||
pipe_through :browser
|
pipe_through [:browser, :ensure_totp]
|
||||||
|
|
||||||
|
# Additional routes for TOTP
|
||||||
|
scope "/registration", Registration, as: :registration do
|
||||||
|
resources "/totp", TotpController, only: [:edit, :update], singleton: true
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/session", Session, as: :session do
|
||||||
|
resources "/totp", TotpController, only: [:new, :create], singleton: true
|
||||||
|
end
|
||||||
|
|
||||||
get "/", ActivityController, :index
|
get "/", ActivityController, :index
|
||||||
|
|
||||||
|
@ -39,7 +54,7 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/comments", CommentController, only: [:index]
|
resources "/comments", CommentController, only: [:index]
|
||||||
|
|
||||||
scope "/filters", Filter, as: :filter do
|
scope "/filters", Filter, as: :filter do
|
||||||
resources "/current", CurrentController, only: [:update], singular: true
|
resources "/current", CurrentController, only: [:update], singleton: true
|
||||||
end
|
end
|
||||||
resources "/filters", FilterController
|
resources "/filters", FilterController
|
||||||
resources "/profiles", ProfileController, only: [:show]
|
resources "/profiles", ProfileController, only: [:show]
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
defmodule PowMultiFactor.Phoenix.ControllerCallbacks do
|
|
||||||
@moduledoc """
|
|
||||||
Controller callback logic for multi-factor authentication.
|
|
||||||
|
|
||||||
### 2FA code not submitted
|
|
||||||
|
|
||||||
Triggers on `Pow.Phoenix.SessionController.create/2`.
|
|
||||||
|
|
||||||
When a user with 2FA enabled attempts to sign in without submitting their
|
|
||||||
TOTP token, the session will be cleared, and the user redirected back to
|
|
||||||
`Pow.Phoenix.Routes.session_path/1`.
|
|
||||||
|
|
||||||
### User updates account
|
|
||||||
|
|
||||||
Triggers on `Pow.Phoenix.RegistrationController.update/2`
|
|
||||||
|
|
||||||
When a user changes their account settings, they are required to confirm a
|
|
||||||
current 2FA token.
|
|
||||||
|
|
||||||
See `PowMultiFactor.Ecto.Schema` for more.
|
|
||||||
"""
|
|
||||||
|
|
||||||
use Pow.Extension.Phoenix.ControllerCallbacks.Base
|
|
||||||
|
|
||||||
alias Pow.Plug
|
|
||||||
alias PowMultiFactor.Plug, as: PowMultiFactorPlug
|
|
||||||
|
|
||||||
def before_respond(Pow.Phoenix.SessionController, :create, {:ok, conn}, config) do
|
|
||||||
return_path = routes(conn).session_path(conn, :new)
|
|
||||||
|
|
||||||
clear_unauthorized(conn, config, {:ok, conn}, return_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def before_respond(Pow.Phoenix.RegistrationController, :update, {:ok, user, conn}, config) do
|
|
||||||
return_path = routes(conn).registration_path(conn, :edit)
|
|
||||||
|
|
||||||
halt_unauthorized(conn, config, {:ok, user, conn}, return_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp clear_unauthorized(conn, config, success_response, return_path) do
|
|
||||||
case PowMultiFactorPlug.mfa_authorized?(conn, config) do
|
|
||||||
false -> clear_auth(conn) |> go_back(return_path)
|
|
||||||
true -> success_response
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp halt_unauthorized(conn, config, success_response, return_path) do
|
|
||||||
case PowMultiFactorPlug.mfa_authorized?(conn, config) do
|
|
||||||
false -> go_back(conn, return_path)
|
|
||||||
true -> success_response
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear_auth(conn) do
|
|
||||||
{:ok, conn} = Plug.clear_authenticated_user(conn)
|
|
||||||
|
|
||||||
conn
|
|
||||||
end
|
|
||||||
|
|
||||||
defp go_back(conn, return_path) do
|
|
||||||
error = extension_messages(conn).invalid_multi_factor(conn)
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> Phoenix.Controller.put_flash(:error, error)
|
|
||||||
|> Phoenix.Controller.redirect(to: return_path)
|
|
||||||
|
|
||||||
{:halt, conn}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,37 +0,0 @@
|
||||||
defmodule PowMultiFactor.Plug do
|
|
||||||
@moduledoc """
|
|
||||||
Plug helper methods.
|
|
||||||
"""
|
|
||||||
|
|
||||||
alias Plug.Crypto
|
|
||||||
alias Pow.Plug
|
|
||||||
alias Pow.Config
|
|
||||||
|
|
||||||
def mfa_authorized?(conn, config) do
|
|
||||||
user = Plug.current_user(conn)
|
|
||||||
|
|
||||||
if user.otp_required_for_login do
|
|
||||||
secret = user.__struct__.otp_secret(user)
|
|
||||||
totp = Elixir2fa.generate_totp(secret)
|
|
||||||
|
|
||||||
Crypto.secure_compare(totp, conn.params)
|
|
||||||
else
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def assign_mfa(conn, config) do
|
|
||||||
user = Plug.current_user(conn)
|
|
||||||
repo = Config.repo!(config)
|
|
||||||
|
|
||||||
if user.encrypted_otp_secret in [nil, ""] do
|
|
||||||
{:ok, user} =
|
|
||||||
user.__struct__.put_otp_secret(Elixir2fa.random_secret())
|
|
||||||
|> repo.update()
|
|
||||||
|
|
||||||
user
|
|
||||||
else
|
|
||||||
user
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -66,8 +66,9 @@ defmodule Search.Parser do
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
{:error, msg}
|
{:error, msg}
|
||||||
|
|
||||||
_ ->
|
err ->
|
||||||
{:error, "unknown parsing error"}
|
err
|
||||||
|
#{:error, "unknown parsing error"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
1
mix.exs
1
mix.exs
|
@ -53,7 +53,6 @@ defmodule Philomena.MixProject do
|
||||||
{:nimble_parsec, "~> 0.5.1"},
|
{:nimble_parsec, "~> 0.5.1"},
|
||||||
{:canary, "~> 1.1.1"},
|
{:canary, "~> 1.1.1"},
|
||||||
{:scrivener_ecto, "~> 2.0"},
|
{:scrivener_ecto, "~> 2.0"},
|
||||||
{:elixir2fa, "~> 0.1.0"},
|
|
||||||
{:pbkdf2, "~> 2.0"}
|
{:pbkdf2, "~> 2.0"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue