What is the best way to build permission and acces manager?
I built custom Plug
which get current_user
from Pow
, route_info
from Phoenix
and get_path
from conn
. But there is few issue.
My code looks like this one
defmodule MyApp.Plug.CheckPermission do
# ... some logic
defp proceed?(conn) do
user = PowPlug.current_user(conn)
[context, module] = get_path(conn)
action = get_action(conn)
permission = "#{context}.#{module}.#{action.plug_opts}"
AccessManager.has_perm?(user.permissions, permission)
end
def get_action(conn) do
Phoenix.Router.route_info(
GamblerWeb.Router,
conn.method,
conn.request_path,
GamblerWeb.Endpoint.host()
)
end
defp get_path(conn) do
case conn.path_info do
[] -> ["", ""]
[context] -> [context, ""]
[context, module | _rest] -> [context, module]
end
end
It works well if all of my routes look like this /<context>/<schema>/<other_content>
.
Our app is getting bigger and bigger so i decided to refactor my router.ex file. And it was huge. Short story,
App is built as 4-parts project.
- Internal API to communicate with our front app based on angular/vue/react
- External API to communicate with external system like payments provider or external source
- Admin Console as simple CRUD for users
- Ajax API to build advanced pages like dynamic category tree
I read that it is common to use few scopes
scope "/api/internal/v1", AppWeb, as: :api_internal do
pipe_through :api_internal
# ...
end
scope "/api/external/v1", AppWeb, as: :api_external do
pipe_through :api_external
# ...
end
scope "/api/ajax/v1", AppWeb, as: :api_ajax do
pipe_through :api_ajax
# ...
end
scope "/", AppWeb do
pipe_through :html
# ...
end
And know i dont know how to pass our permission to ajax scope. It’s important to check user permission on ajax requesty, but our solution doesn’t allow to start path from /api/ajax
.
Is there any good option?
For example we could have 3 context. All of them, except permissions
are basic CRUD operation
Accounts.Account [:create, :read: update, :delete]
Account.Permissions [:toggle]
Blog.Category [:create, :read: update, :delete]
Blog.Post [:create, :read: update, :delete]
Examples user permission:
# this user can do anything on category
[
"blog.category.create",
"blog.category.update",
"blog.category.read",
"blog.category.delete",
]
How to deal with it?
And second question. It is good option to separate directory like below?
controllers/api_external/accounts/account.ex
controllers/api_external/accounts/permissions.ex
controllers/api_external/blog/category.ex
controllers/api_external/blog/post.ex
controllers/api_internal/accounts/account.ex
controllers/api_internal/accounts/permissions.ex
controllers/api_internal/blog/category.ex
controllers/api_internal/blog/post.ex
controllers/api_ajax/accounts/account.ex
controllers/api_ajax/accounts/permissions.ex
controllers/api_ajax/blog/category.ex
controllers/api_ajax/blog/post.ex
controllers/html/accounts/account.ex
controllers/html/accounts/permission.ex
controllers/html/blog/category.
controllers/html/blog/post.ex
The same on view
.
Actual i have only api_external
, api_internal
and html + ajax
. But there is one issue. Default traceback on 404 page in html scope render 404 page instead json 404 page. It is possible to fix by adding case or with but it is still lot of redundant logic.
So there is few topic in one post, but they are closely related.
- scope in route - best practice
- directories path - best practice
- custom plug and acces manager - how to get current path and action