jstlroot

jstlroot

Good day to you all, beloved Elixir community! :heart:

I was wondering if, for security reasons, introspection of certain fields could be restricted.

Being able to put restrictions based on certain conditions would be very cool but I don’t even need that level of control. A hard block would do the trick just fine.

@benwilson512 or anyone else, any idea / suggestion / observation?

If not, is there a way to totally disable introspection?

Any input is appreciated.

Cheers! :beers:

Showing Posts 1 to 8

halostatue

halostatue

I’m not sure that would be useful, as the introspection is often required by clients to generate query code.

0x6a68

0x6a68

Sure, a middleware should do the job.

Francesco

Francesco

Did you end up figuring out how to do this?

I would like to disable introspection of fields depending on whether the current user is an admin or not (normal users shouldn’t need to see that there are fields that are meant only for admins)

sergio

sergio

One of the engineers on my team and I were discussing this a few days ago.

Two options we thought about:

  1. Have each field call/3 a resolver that checks against the schema for “normal user fields”, “admin only fields”. If atom in list, is the conditional of this resolver.

  2. Have two separate graphql objects, one for a admin viewing ObjectFoobar, and one for the end user viewing ObjectFoobar.


Option 1, you put the load on your server more (haven’t measured performance impact), but your frontend/mobile can just call ObjectFoobar and not care if the user is admin or normal.

Option 2, your server is lighter, but your frontend/mobile now needs to worry about calling admin ObjectFoobar or end user ObjectFoobar.

Curious of other approaches.

halostatue

halostatue

The two choices here are basically the real options; to change the introspection would require diving into parts of Absinthe that would potentially break compatibility with GraphQL clients.

I have sort of done both directions, although for different reasons. As an example, I have an Invoice object (we use Relay):

node object(:invoice) do
  field :invoice_date, :date
  field :sales, :integer
  field :margin, :integer, resolve: &InvoiceResolver.margin/3
  field :cost, :integer, resolve: &InvoiceResolver.cost/3
  field :profit, :integer, resolve: &InvoiceResolver.profit/3
end

In my InvoiceResolver, I have some /3 resolvers:

defmodule InvoiceResolver do
  # various includes and aliases, etc., including `Canada.can?/2`
  def margin(%{margin: value} = invoice, _, %{context: %{current_user: current_user}}) do
    if can?(current_user, show, {invoice, :margin}) do
      {:ok, value}
    else
      {:ok, nil}
    end
  end

  # &c…
end

This is actually important from my perspective because some employees can see the cost, profit, and margin…but others cannot (and customers definitely cannot).

In a different scenario, I’ve added a second Absinthe schema (plus endpoint and Absinthe.Plug instance). I’ve come up with some ways of sharing some of the definitions between the two schemas and some of the resolvers, but the is is less satisfying to me overall. The reason that I did it this way was that the purpose of the two APIs was different (one is primarily for consumption and customer-centric interaction; the other is primarily for creation and employee-centric interaction). We are thinking of adding a third schema for an administrative API, but I’d need to come up with some better abstractions about managing multiple related schema before I chase this further.

Francesco

Francesco

Interesting, @halostatue, @sergio seems like you have both come to the same conclusions. Either do a check in each resolver to see if the user can access this field but then every resolver needs another check, or have another schema.

I had experimented with adding an Absinthe middleware instead of placing logic in the resolvers. I think this might be a little nicer than the resolver approach since it centralises this checking logic. The following is what I have so far, its very simple atm. It just checks whether you are an admin or not depending on how I call it, but it could easily be extended to become more granular:

defmodule ScribeWeb.Schema.Middleware.Authorize do
  @moduledoc """
  Authorizes the user to access a given field in the schema based off their role
  """
  @behaviour Absinthe.Middleware

  def call(resolution, role \\ :user) do
    case resolution.context do
      %{authenticated: true, current_user: user} ->
        if role == :moderator do
          case Scribe.Admin.get_moderator(user.id) do
            {:ok, _} ->
              resolution

            _ ->
              resolution
              |> Absinthe.Resolution.put_result({:error, message: "unauthorized"})
          end
        else
          resolution
        end

      _ ->
        resolution
        |> Absinthe.Resolution.put_result({:error, message: "unauthorized"})
    end
  end
end

and you can invoke it like:

    @desc "Get all users"
    field(:all_users, list_of(:user)) do
      middleware(Middleware.Authorize, :moderator)
      resolve(&Accounts.list_users/3)
    end

Only bummer with this is that anyone can still introspect my schema and see that the all_users query exists, they just can’t call it. I would love a way to be able to stop normal users from even being able to see it exists (in the case I offered an API).

@halostatue does that mean that restricting visibility of introspection is actually not part of the GraphQL spec?

halostatue

halostatue

I’m pretty sure that blocking partial introspection would be contra-spec, and would not help you in the cases where you might be generating code to hit your administrative API. This would mean that, if you built an iOS app using apollo-ios, your build system would have to have a moderator/admin account in order to get your GraphQL schema.

If you really want to make that work, use two different schema. You could define all of your types in common and import_types into each of your regular user and admin/moderator schema (and maybe even make it so that the admin/moderator schema is a strict superset of the regular user schema). You’ll need different endpoints, and your authentication system would have to somehow return which endpoint would be required for which type of user, but…

The main reason I haven’t done mine as middleware is that when I need to do authorization checks, I typically have to do so against the object being checked.

Ahmad

Ahmad

For anyone looking to disable introspection completely or based on some context (e.g an authorized client, codegen tool, etc), this is a simple solution using an Absinthe plugin: Disable GraphQL schema introspection in Elixir Absinthe using a plugin · GitHub

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews