View Source LiveGuard
A simple package to protect the LiveView lifecycle stages such as :mount, :handle_params, :handle_event, :handle_info and :handle_async.
Installation
For the latest master:
def deps do
[
{:live_guard, github: "FabianDaniel00/live_guard"}
]
endFor the latest release:
def deps do
[
{:live_guard, "~> 0.1.8"}
]
endThen run mix deps.get to fetch the dependencies.
Config
:current_userYou need to assign the current user to the socket before LiveGuard
on_mount/4callback is called.. The default assign name for the current user is:current_user. If you assign the current user as another than:current_useryou can set in the config:config :live_guard, :current_user, :user:unauthorized_handlerThis function handles unauthorized LiveView lifecycle stages. It's called when the
allowed?/4function returnsfalse.By default it will put an error flash message with text "You don't have permission to do that!".
:mountand:handle_paramsLiveView lifecycle stages needs redirect after it detected as unauthorized. In this case by default it will redirect to the home page (/).You can set a custom handler in the config:
config :live_guard, :unauthorized_handler, {MyModule, :my_handle_unauthorized}It's called with 2 inputs, first is a
socket, second isis_redirect(boolean).
Usage
LiveGuard provide an on_mount/4 callback which can be used in Phoenix LiveViews. Read the docs.
Since this is an on_mount/1 callback you can use it three ways:
- with
live_session/3to protect a couple of LiveViews lifecycle stages at once:# lib/my_app_web/router.ex defmodule MyAppWeb.Router do use MyAppWeb, :router live_session :default, on_mount: LiveGuard do # routes... end end - if you want to protect LiveView lifecycle stages in every LiveViews, you can achive that with the following code:
# lib/my_app_web.ex defmodule MyAppWeb do # some code... def live_view do quote do use Phoenix.LiveView, layout: {MyAppWeb.Layouts, :app} on_mount LiveGuard unquote(html_helpers()) end end # some code... end - and if you want to protect an individual LiveView lifecycle stages you can achive that with the following code:
# lib/my_app_web/live/my_module_live.ex defmodule MyAppWeb.MyModuleLive do use MyAppWeb, :live_view on_mount LiveGuard def mount(_params, _session, socket) do {:ok, socket} end # some code... end
Implementation
For now you should ask, okay but how it will know how to protect the LiveView lifecycle stages?
You need to implement allowed?/4 protocol functions.
The first input of allowed?/4 function is the user, the second is the LiveView module, the third is the LiveView lifecycle stage and the last is LiveView lifecycle stage inputs. In this way you can pattern match to your needings. You can put this file anywhere but /lib/my_app_web/live/abilities.ex is recommended.
It must return a boolean.
# /lib/my_app_web/live/abilities.ex
defimpl LiveGuard.Allowed, for: User do
@before_compile {LiveGuard, :before_compile_allowed}
def allowed?(
%User{role: role},
MyModuleLive,
:handle_event,
{"delete_item", _unsigned_params, _socket}
)
when role in [:viewer, :customer],
do: false
# other `allowed?/4` functions...
endNote: As you can see, you don't have to define catch-all
allowed?/4function because we used@before_compile {LiveGuard, :before_compile_allowed}hook. It returnstrue. This is optional.
If the user is not authenticated you can add the following implementation as below:
defimpl LiveGuard.Allowed, for: Atom do
@before_compile {LiveGuard, :before_compile_allowed}
def allowed?(nil, MyModuleLive, :handle_event, {"delete_item", _unsigned_params, _socket}),
do: false
# other `allowed?/4` functions...
endOptimization (optional)
By default if you use the on_mount/4 callback of LiveGuard, it will attach hooks to attachable LiveView lifecycle stages (:handle_params, :handle_event, :handle_info and :handle_async).
If you need to protect for example only the :handle_event LiveView lifecycle stage for an individual LiveView module you can use this function.
You can put this file anywhere but /lib/my_app_web/live/guarded_stages.ex is recommended.
It must return a list of valid attachable LiveView lifecycle stages (unless :after_render).
Example
# /lib/my_app_web/live/guarded_stages.ex
defimpl LiveGuard.GuardedStages, for: Atom do
@before_compile {LiveGuard, :before_compile_guarded_stages}
def guarded_stages(MyModuleLive), do: [:handle_event]
# other `guarded_stages?/1` functions...
endIn this case it will only attach hook to :handle_event LiveView lifecycle stage.
Note: As you can see, you don't have to define catch-all
guarded_stages/1function because we used@before_compile {LiveGuard, :before_compile_guarded_stages}hook. It returns the valid attachable LiveView lifecycle stages (:handle_params,:handle_event,:handle_infoand:handle_async). This is optional.
License
MIT License. Copyright 2023 Daniel Fabian.
Few words from the author
This package is inspired by canary.