本文属使用Prisma构建GraphQL服务系列。
在本教程中,您将学习如何在使用Prisma和graphql-yoga
构建GraphQL服务器时实施权限规则。
为了达到本教程的目的,您将从使用 node-advanced 的GraphQL样板项目(已经附带开箱即用的认证)开始。然后,您将逐渐调整现有的解析器以解决API的权限需求。开搞!
引导GraphQL服务
在使用graphql create
引导GraphQL服务之前,您需要先安装GraphQL CLI。
(1) 打开终端,安装GraphQL CLI:
npm install -g graphql-cli
注意:为了本教程目的,您不必明确地安装Prisma CLI,因为
prisma
在node-advanced
样板中被列为开发依赖(package.json中devDependencies
)项,允许通过在命令前加上yarn
前缀来运行,如:yarn prisma deploy 或
yarn prisma playground。如果您的机器上全局安装了
prisma(使用npm install -g prisma全局安装),则在本教程中无需使用
yarn`前缀。
一旦CLI安装完毕,您可以创建您的GraphQL服务。
(2) 在终端中,切换到到您选择的目录并运行以下命令:
graphql create permissions-example --boilerplate node-advanced
(3) 当提示您在何处(即向哪个群集cluster)部署您的Prisma服务时,请选择其中一个公共群集选项:prisma-eu1
或 prisma-us1
。
注意:您也可以在本地部署Prisma服务,但这需要您在机器上安装Docker。为了本教程的目的,我们将简单直接的使用公共集群。
这将创建一个名为permissions-example
的新目录,其中它将放置GraphQL服务的源文件(基于graphql-yoga
)以及所属的Prisma database服务所需的配置。
GraphQL服务基于以下数据模型(data model):
type Post {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!
isPublished: Boolean! @default(value: "false")
title: String!
text: String!
author: User!
}
type User {
id: ID! @unique
email: String! @unique
password: String!
name: String!
posts: [Post!]!
}
添加ADMIN
角色
在本教程中,一个User
可以是管理员(具有特殊访问权限),也可以是简单的客户。要区分这些类型的User
,您需要修改数据模型并添加一个定义这些角色的枚举。
(4) 打开 database/datamodel.graphql
并更新数据模型中的User
类型,如下所示,请注意,您还需要添加Role
枚举:
type User {
id: ID! @unique
email: String! @unique
password: String!
name: String!
posts: [Post!]!
+ role: Role! @default(value: "CUSTOMER")
}
enum Role {
ADMIN
CUSTOMER
}
请注意,role
字段不会通过您的GraphQL服务的API公开(就像password
字段一样),因为在application schema中定义的User
类型中没有它。application schema只定义将向客户端公开的哪些数据。
需要部署以应用更改。
(5) 在permissions-example
目录中,运行以下命令:
yarn prisma deploy
现在您的数据模型和Prisma API被更新并包含User
类型的role
字段。
定义权限需求
在src/schema.graphql
中定义的application schema暴露了以下查询(queries)和突变(mutations):
type Query {
feed: [Post!]!
drafts: [Post!]!
post(id: ID!): Post!
me: User
}
type Mutation {
signup(email: String!, password: String!, name: String!): AuthPayload!
login(email: String!, password: String!): AuthPayload!
createDraft(title: String!, text: String!): Post!
publish(id: ID!): Post!
deletePost(id: ID!): Post!
}
目前,我们只对与Post
类型相关的解析器(Resolvers)感兴趣。以下是我们对其的权限要求:
-
feed
:没有权限要求。所有人(不仅是经过身份验证的用户)应该能够访问feed
——发布的Post
节点。 -
drafts
:每个用户应该只能访问他们自己的草稿,即Post
的作者(author
)。 -
post
:只有post
作者(author
)或ADMIN
用户才能够使用post
查询访问Post
节点。 -
publish
:只有Post
的作者才能发布它。 -
deletePost
:只有Post
的作者(author
)或ADMIN
用户才能删除它。
实现权限规则:使用graphql-yoga
和Prisma
在使用Prisma和graphql-yoga
实现权限规则时,基本思想是在每个解析器中实施“数据访问检查(data access check)”。只有检查成功,操作(查询,变异或订阅)才会使用可用的prisma-binding
转发到Prisma服务。
现在逐渐将这些“数据访问检查”添加到现有的解析器中。
feed
由于所有人都可以访问feed
查询,因此不需要在此处执行检查。
drafts
对于drafts
查询,我们有以下需求:
每个用户应该只能访问他们自己的草稿,即
Post
的作者(author
)。
目前,drafts
解析器的实现如下:
drafts(parent, args, ctx, info) {
const id = getUserId(ctx)
const where = {
isPublished: false,
author: {
id
}
}
return ctx.db.query.posts({ where }, info)
},
事实上,这已经满足了这个需求,因为它过滤了posts
,仅检索当前登录用户的Post
数据。所以,此处就这样。
post
对于post
查询,我们有以下要求:
只有
post
作者(author
)或ADMIN
用户才能够使用post
查询访问Post
节点。
以下是当前实现:
post(parent, { id }, ctx, info) {
return ctx.db.query.post({ where: { id } }, info)
}
如此简单直接!但是现在,如果发送请求的User
是其作者(author
)或ADMIN
用户,那么您需要确保它仅返回一个Post。
(6) 更新src/resolvers/Query.js
中解析器的实现,如下所示:
async post(parent, { id }, ctx, info) {
const userId = getUserId(ctx)
const requestingUserIsAuthor = await ctx.db.exists.Post({
id,
author: {
id: userId,
},
})
const requestingUserIsAdmin = await ctx.db.exists.User({
id: userId,
role: 'ADMIN',
})
if (requestingUserIsAdmin || requestingUserIsAuthor) {
return ctx.db.query.post({ where: { id } }, info)
}
throw new Error(
'Invalid permissions, you must be an admin or the author of this post to retrieve it.',
)
}
通过这两个exists
的调用,您可以收集有关以下内容的信息:
- 发送请求的
User
实际上是被请求的Post
的作者(author
)。 - 发送请求的
User
的是ADMIN
。
如果这些条件中的任何一个为真,您只需返回Post
,否则返回权限不足的错误信息。
publish
publish
突变具有以下要求:
只有
Post
的作者才能发布它。
发布解析器在src/resolvers/Mutation/post.js
中实现,目前看起来如下所示:
async publish(parent, { id }, ctx, info) {
const userId = getUserId(ctx)
const postExists = await ctx.db.exists.Post({
id,
author: { id: userId },
})
if (!postExists) {
throw new Error(`Post not found or you're not the author`)
}
return ctx.db.mutation.updatePost(
{
where: { id },
data: { isPublished: true },
},
info,
)
},
当前exists
的调用已经确保发送请求的User
被设置为要发布的Post
的作者(author
)。所以,你实际上也不必做任何改变,而且这项要求已经被满足了。
deletePost
deletePost
突变有以下要求:
只有
Post
的作者(author
)或ADMIN
用户才能删除它。
当前的解析器在src/resolvers/Mutation/post.js
中实现,如下所示:
async deletePost(parent, { id }, ctx, info) {
const userId = getUserId(ctx)
const postExists = await ctx.db.exists.Post({
id,
author: { id: userId },
})
if (!postExists) {
throw new Error(`Post not found or you're not the author`)
}
return ctx.db.mutation.deletePost({ where: { id } })
},
又一次,exists
调用已确保请求User
是要删除的Post
的作者author
。但是,如果该用户是ADMIN
,则该帖子仍应被删除。
(7) 调整src/resolvers/Mutation/post.js
中的deletePost
解析器,如下所示:
async deletePost(parent, { id }, ctx, info) {
const userId = getUserId(ctx)
const postExists = await ctx.db.exists.Post({
id,
author: { id: userId },
})
+ const requestingUserIsAdmin = await ctx.db.exists.User({
+ id: userId,
+ role: 'ADMIN',
+ })
* if (!postExists && !requestingUserIsAdmin) {
throw new Error(`Post not found or you don't have access rights to delete it.`)
}
return ctx.db.mutation.deletePost({ where: { id } })
},
权限测试
您可以在GraphQL Playground中获得权限。以下是流程:
- 在Playground中,使用
signup
突变创建一个新User
,并选择(selection)中指定令牌(token
),以便服务端返回该令牌(token
)(如果您以前已经创建了用户,则当然也可以使用login
突变) - 将服务端返回中的令牌保存起来,并将其设置为Playground中的
Authorization
标头(header
)(您将学习如何做到这一点) - 所有后续请求现在都代表该用户(
User
)发送
1.创建新User
你首先需要打开一个GraphQL Playground,但在这之前,你需要启动服务!
(8) 在permissions-example
目录中,在终端中运行以下命令:
yarn start
服务现在运行在 http://localhost:4000.
(9) 浏览器中打开 http://localhost:4000
(10) 在默认(default)的Playground中app部分,发送以下突变:
mutation {
signup(
email: "[email protected]"
password: "graphql"
name: "Sarah"
) {
token
}
}
(11) 复制令牌(token
)并将其设置为Playground左下角的Authorization
标头(header)。您需要将标头(header)设置为JSON,如下所示(请注意,您需要将TOKEN占位符替换为从signup
突变返回的身份验证令牌token):
{
"Authorization": "__TOKEN__"
}
从现在开始,通过Playground发送的所有请求均代表您刚刚创建的用户(User
)发送。
配备这些知识,您现在可以利用可用的查询和变异来验证权限规则是否有效。
例如,您可以通过以下流程:
- 代表
Sarah
(您刚创建的用户)创建一个包含createDraft
突变的新草稿。 - 使用
signup
突变创建另一个用户并为他们请求一个令牌(token
)。 - 使用新的身份验证令牌尝试发布Sarah的草稿。应该会返回以下错误:
Post not found or you don't have access rights to delete it.
。