hello everyone,
I am trying to build an ecosystem on top of OTP, where I can create really well defined OTP apps and connected together so I can reuse it cross multiple businesses.
Even when I will use GraphQL with Absinthe this is an issue no matter what
Right now, I am using GraphQL (with Absinthe) for the IO throw HTTP. My expectations is to move the GraphQL schemas to the OTP apps, so every single app knows about it own GraphQL structure.
But I have a problem: Authorization.
Use Canada package keep in my this code
defimpl Canada.Can, for: StrawHat.Schema.UserSchema do
# ... other validations
def can?(_, _, _), do: false
end
defimpl Canada.Can, for: StrawHat.Schema.UnauthenticatedUserSchema do
# other validations
def can?(_, action, _) when action in [
:register_user, :create_session], do: true
def can?(_, _, _), do: false
end
You could see those modules as my current role, which it’s actually the type of user but it’s the same for my current application.
Because authorization is something that is attach to the user and/or role I can’t move right now those schemas outside of my monolith GraphQL endpoint (you could see it as specific business)
Example of Schema
object :user_mutations do
field :create_admin, :user do
arg :input, non_null(:user_input)
# this is where the authorization happens
# basically forwards the current user to the Canada implementation
middleware AuthorizationMiddleware, [action: :create_admin]
resolve &UserResolver.create_admin_user/2
middleware ErrorFormatterMiddleware
end
end
Middleware implementation
defmodule StrawHat.GraphQL.Middleware.AuthorizationMiddleware do
@behaviour Absinthe.Middleware
alias Absinthe.Resolution
alias StrawHat.Schema.UnauthenticatedUserSchema
def call(%{context: context, state: :unresolved} = resolution, [action: action]) do
current_user = Map.get(context, :current_user, %UnauthenticatedUserSchema{})
# pretty simple, I am just using Canada with the current authenticated user if there is any
if Canada.Can.can?(current_user, action, context) do
resolution
else
Resolution.put_result(resolution, {:error, {:unauthorized, "You are not allow to perform this operation"}})
end
end
def call(resolution, _), do: resolution
end
Because in that specific GraphQL endpoint live on specific business and that business have it own way to define how many type of users (which right now are roles as well) now I can’t create a truly independent OTP app because it’s have to know about the users roles.
Btw, I could move the authorization piece into my OTP apps which will be ideally because nobody have to implement anything related to authorization but, I can’t or actually I don’t know how to do it without attach every single OTP to specific business structure.
So the first phase would be to actually put those things inside a database so I can make it dynamic but that’s where I don’t know what is the best architecture for this, I am reading about Amazon Policies and it seems to be legic, I could actually force everything to pass a policy to my OTP module and with that policy I could check if you are authorize to perform the operation. Also, I am confused how to handle authorization per fields base, for example lunch_misil
property which only specific rule could allow todo this. And then: read , write, execute a module and so on use cases that probably older programmers know about it
But I know for sure I will miss something I need your help to tackle this issue that could actually help everyone in the community. I would love if you could help me out because my knowledge is limited in this case on best practices and I want to think something independent of my business right now, because I know it will be a problem on my second business
Any link, video or knowledge that you can share will be appreciated.