GraphQL: Conhecendo o GraphQL Shield

Permissões em GraphQL como outra camada de abstração.

Image for post
Image for post
Um modo organizado de adicionar permissões em sua aplicação! — Créditos Imagem

Sistemas de permissões são o núcleo de todas as aplicações. A maioria dos servidores depende totalmente da manipulação de acesso a dados, portanto, as permissões de gravação devem ser simples, mas poderosas.

O GraphQL Shield fornece uma abordagem direta para permissões de escrita em servidores GraphQL. Seu foco principal é abstrair a camada de permissão de um modo eficiente, em uma camada lógica do nosso servidor. No entanto, seu poder real vem com o cache dinâmico, que reduz significativamente a carga em seu servidor e resulta em uma avaliação mais rápida da consulta GraphQL.

A.G. — Antes do GraphQL ⏰

Tratar solicitações, uma a uma, é simples. Cada endereço da API REST pode ser traduzido em uma resposta precisa que requer, além da lógica de negócios do aplicativo, ajustes precisos na proteção de dados. No entanto, tem um fluxo significativo — não é eficiente para o cliente. GraphQL aborda isso de um modo incrível. Ao oferecer o poder de seleção de dados ao cliente, reduzimos o número de chamadas na rede e melhoramos a velocidade do aplicativo.

Os servidores REST não exigiam armazenamento em cache dinâmico. O servidor iria processar cada solicitação independentemente das outras; portanto, nossas mãos estavam mais ou menos atadas. GraphQL, por outro lado, obtém dados recursivamente. Nossas informações não são mais obtidas uma a uma, mas sim por completo. Por causa disso, devemos reconsiderar a troca de permissões estáticas e antigas para uma camada de permissões ativas, que armazena informações de maneira inteligente.

Entra GraphQL Shield 🎉

O GraphQL Shield gerencia a camada de permissões de maneira inteligente e possui uma API intuitiva. O processo de implementação do Shield em aplicativos existentes consiste em duas etapas — definir regras e atribuí-las a esquemas, tipos ou campos da sua aplicação. Para melhor apresentar a idéia disso, vamos construir um pequeno aplicativo de uma Quitanda. Em nosso aplicativo, queremos garantir que as pessoas sem conta possam ver os produtos, os que estão comprando podem ver seus preços e adicioná-los à cesta, e o proprietário da loja pode adicionar ou remover produtos existentes do estoque. Além disso, também queremos garantir que apenas o administrador possa atuar como proprietário de uma loja e que apenas pessoas logadas no sistema possam adicionar itens a seus carrinhos de compras. Para seguir este passo a passo mais facilmente, recomendo que você baixe o repositório que contém o código final:

Maçã, Banana, Laranja — O Modelo 🗺

Vamos começar criando um modelo de dados simples. Nosso aplicativo será composto por quatro tipos; estes são o Grocer, ou o dono da loja, o Customer, o BasketItem e o Product. Nós vamos usar o Prisma para gerenciar nossos dados; portanto, resumimos isso no seguinte arquivo:

# database/datamodel.graphql

type Grocer {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!

email: String! @unique
}

type Customer {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!

email: String! @unique
basket: [BasketItem!]!
}

type BasketItem {
id: ID! @unique
product: Product!
quantity: Int!
}

type Product {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!

name: String!
description: String!
price: Int!
}

Resolvers — Fazendo o Suco ⚙️

Agora que temos nosso modelo de dados construído, vamos pensar em funcionalidade. Como dissemos, queremos apresentar os produtos disponíveis para todos. Queremos permitir que nossos clientes vejam os preços de seus produtos e os adicionem à cesta. Além disso, queremos garantir que o dono da loja possa adicionar ou remover novos produtos do inventário e gerenciar o suprimento. Não vou explicar detalhadamente a mecânica por trás de cada um dos resolvers, pois esse é tópico de outro artigo, mas, ao invés disso, vou apresentar o esquema em si, que deve nos fornecer informações suficientes para avançar para a próxima etapa:

# src/schema.graphql

type Query {
viewer: Viewer
products: [Product!]!
}

type Mutation {
addItemToBasket(productId: ID!): Viewer
removeItemFromBasket(itemId: ID!): Viewer
addProduct(name: String!, description: String!, price: Int!): Product!
removeProduct(id: ID!): Product!
}

type Viewer {
email: String!
basket: [ProductItem!]!
}

Laranja pode, mas a Banana não pode — Permissões 🔒

Agora é hora de começarmos a pensar em permissões. Temos três estados — um usuário não autenticado, um usuário autenticado que também é um cliente e um usuário autenticado que é o dono. Podemos resumir esses três estados no arquivo abaixo.

// src/permissions/rules.ts
import { rule, and, or, not } from 'graphql-shield'
import { Context, getUserEmail } from '../utils'

export const isGrocer = rule()(async (parent, args, ctx: Context, info) => {
const email = getUserEmail(ctx)
// Existe algum "Dono" com esse email no banco de dados (Prisma)?
return ctx.db.exists.Grocer({ email })
})

export const isCustomer = rule()(
async (parent, args, ctx: Context, info) => {
const email = getUserEmail(ctx)
// Existe algum "Cliente" com esse email no banco de dados (Prisma)?
return ctx.db.exists.Customer({ email })
},
)

export const isAuthenticated = or(isCustomer, isGrocer)

Simples, não? Nós definimos um bloco com a função rule. Essa função aceita dois parâmetros adicionais (rule(<name>, <options>), mas não requer nenhum deles. Poderíamos passar opções adicionais ao rule e dizer que não queremos fazer o cache de uma regra X ou passar um nome específico que será usado internamente. Shield faz o cache de todas as regras por padrão, melhorando o tempo de consulta. Por causa disso, não há necessidade de opções adicionais se você não estiver planejando usar o Shield em casos avançados. O mesmo vale para a propriedade name. Shield garante por padrão que nenhum nome seja duplicado e todos os nomes sejam atribuídos corretamente. Vale ressaltar, entretanto, que se você precisar de tal funcionalidade, você pode ler mais sobre isso na documentação.

O último passo na implementação da lógica de permissão é definir quais regras devem cobrir quais campos. O Shield é bem flexível quando se trata de atribuir sua lógica de regras ao seu esquema. No nosso exemplo, definimos todas as permissões em um arquivo, já que nosso aplicativo é bastante simples. Para gerar o middleware do Shield, usamos a função shield:

// src/permissions/index.ts

import { shield, and } from 'graphql-shield'
import * as rules from './rules'

export const permissions = shield({
Query: {
viewer: rules.isGrocer,
},
Mutation: {
addItemToBasket: rules.isCustomer,
removeItemFromBasket: rules.isCustomer,
addProduct: rules.isGrocer,
removeProduct: rules.isGrocer,
},
Product: {
price: rules.isAuthenticated,
},
})

// src/index.ts
import { permissions } from './permissions'

const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
middlewares: [permissions],
context: req => ({
...req,
db: new Prisma({
endpoint: process.env.PRISMA_ENDPOINT,
debug: false,
secret: process.env.PRISMA_SECRET,
}),
}),
})
server.start(() => console.log(`Server is running on http://localhost:4000`))

Além disso, podemos definir permissões para cada parte de nosso aplicativo separadamente e mesclá-las usando o operador spread do ES6 ou usar o método Object.assign.

Neste artigo, aprendemos a usar o Shield. Nós criamos um aplicativo de Mercadinho e aplicamos permissões ao nosso esquema. Dessa forma, conseguimos limitar o acesso à funcionalidade em nosso aplicativo e obter controle sobre nossos dados.

Este artigo é um dos exemplos de como usar o Shield com o seu servidor GraphQL. Se você gostou, mas não entendeu tudo, deixe um comentário abaixo ou me envie uma mensagem direta no Twitter, onde você pode me encontrar como @maticzav.

Se você gostou do projeto GraphQL Shield, apoie-nos, tornando-se um colaborador do OpenCollective! ❤️

Cheers! 🍻

Créditos ⭐️

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store