本文屬使用Prisma構(gòu)建GraphQL服務(wù)系列瘟忱。
在本教程中奥额,您將學(xué)習(xí)如何在使用Prisma和graphql-yoga
構(gòu)建GraphQL服務(wù)器時實施權(quán)限規(guī)則。
為了達(dá)到本教程的目的访诱,您將從使用 node-advanced 的GraphQL樣板項目(已經(jīng)附帶開箱即用的認(rèn)證)開始垫挨。然后,您將逐漸調(diào)整現(xiàn)有的解析器以解決API的權(quán)限需求触菜。開搞九榔!
引導(dǎo)GraphQL服務(wù)
在使用graphql create
引導(dǎo)GraphQL服務(wù)之前,您需要先安裝GraphQL CLI。
(1) 打開終端哲泊,安裝GraphQL CLI:
npm install -g graphql-cli
注意:為了本教程目的剩蟀,您不必明確地安裝Prisma CLI,因為
prisma
在node-advanced
樣板中被列為開發(fā)依賴(package.json中devDependencies
)項切威,允許通過在命令前加上yarn
前綴來運行育特,如:yarn prisma deploy 或
yarn prisma playground。如果您的機(jī)器上全局安裝了
prisma(使用npm install -g prisma全局安裝)先朦,則在本教程中無需使用
yarn`前綴缰冤。
一旦CLI安裝完畢,您可以創(chuàng)建您的GraphQL服務(wù)喳魏。
(2) 在終端中棉浸,切換到到您選擇的目錄并運行以下命令:
graphql create permissions-example --boilerplate node-advanced
(3) 當(dāng)提示您在何處(即向哪個群集cluster)部署您的Prisma服務(wù)時,請選擇其中一個公共群集選項:prisma-eu1
或 prisma-us1
刺彩。
注意:您也可以在本地部署Prisma服務(wù)涮拗,但這需要您在機(jī)器上安裝Docker。為了本教程的目的迂苛,我們將簡單直接的使用公共集群。
這將創(chuàng)建一個名為permissions-example
的新目錄鼓择,其中它將放置GraphQL服務(wù)的源文件(基于graphql-yoga
)以及所屬的Prisma database服務(wù)所需的配置三幻。
GraphQL服務(wù)基于以下數(shù)據(jù)模型(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
可以是管理員(具有特殊訪問權(quán)限)呐能,也可以是簡單的客戶念搬。要區(qū)分這些類型的User
,您需要修改數(shù)據(jù)模型并添加一個定義這些角色的枚舉摆出。
(4) 打開 database/datamodel.graphql
并更新數(shù)據(jù)模型中的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服務(wù)的API公開(就像password
字段一樣),因為在application schema中定義的User
類型中沒有它象踊。application schema只定義將向客戶端公開的哪些數(shù)據(jù)温亲。
需要部署以應(yīng)用更改。
(5) 在permissions-example
目錄中杯矩,運行以下命令:
yarn prisma deploy
現(xiàn)在您的數(shù)據(jù)模型和Prisma API被更新并包含User
類型的role
字段栈虚。
定義權(quán)限需求
在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
類型相關(guān)的解析器(Resolvers)感興趣史隆。以下是我們對其的權(quán)限要求:
-
feed
:沒有權(quán)限要求魂务。所有人(不僅是經(jīng)過身份驗證的用戶)應(yīng)該能夠訪問feed
——發(fā)布的Post
節(jié)點。 -
drafts
:每個用戶應(yīng)該只能訪問他們自己的草稿,即Post
的作者(author
)粘姜。 -
post
:只有post
作者(author
)或ADMIN
用戶才能夠使用post
查詢訪問Post
節(jié)點鬓照。 -
publish
:只有Post
的作者才能發(fā)布它。 -
deletePost
:只有Post
的作者(author
)或ADMIN
用戶才能刪除它相艇。
實現(xiàn)權(quán)限規(guī)則:使用graphql-yoga
和Prisma
在使用Prisma和graphql-yoga
實現(xiàn)權(quán)限規(guī)則時颖杏,基本思想是在每個解析器中實施“數(shù)據(jù)訪問檢查(data access check)”。只有檢查成功坛芽,操作(查詢留储,變異或訂閱)才會使用可用的prisma-binding
轉(zhuǎn)發(fā)到Prisma服務(wù)。
現(xiàn)在逐漸將這些“數(shù)據(jù)訪問檢查”添加到現(xiàn)有的解析器中咙轩。
feed
由于所有人都可以訪問feed
查詢获讳,因此不需要在此處執(zhí)行檢查。
drafts
對于drafts
查詢活喊,我們有以下需求:
每個用戶應(yīng)該只能訪問他們自己的草稿丐膝,即
Post
的作者(author
)。
目前钾菊,drafts
解析器的實現(xiàn)如下:
drafts(parent, args, ctx, info) {
const id = getUserId(ctx)
const where = {
isPublished: false,
author: {
id
}
}
return ctx.db.query.posts({ where }, info)
},
事實上帅矗,這已經(jīng)滿足了這個需求,因為它過濾了posts
煞烫,僅檢索當(dāng)前登錄用戶的Post
數(shù)據(jù)浑此。所以,此處就這樣滞详。
post
對于post
查詢凛俱,我們有以下要求:
只有
post
作者(author
)或ADMIN
用戶才能夠使用post
查詢訪問Post
節(jié)點。
以下是當(dāng)前實現(xiàn):
post(parent, { id }, ctx, info) {
return ctx.db.query.post({ where: { id } }, info)
}
如此簡單直接料饥!但是現(xiàn)在蒲犬,如果發(fā)送請求的User
是其作者(author
)或ADMIN
用戶,那么您需要確保它僅返回一個Post岸啡。
(6) 更新src/resolvers/Query.js
中解析器的實現(xiàn)原叮,如下所示:
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
的調(diào)用,您可以收集有關(guān)以下內(nèi)容的信息:
- 發(fā)送請求的
User
實際上是被請求的Post
的作者(author
)巡蘸。 - 發(fā)送請求的
User
的是ADMIN
篇裁。
如果這些條件中的任何一個為真,您只需返回Post
赡若,否則返回權(quán)限不足的錯誤信息达布。
publish
publish
突變具有以下要求:
只有
Post
的作者才能發(fā)布它。
發(fā)布解析器在src/resolvers/Mutation/post.js
中實現(xiàn)逾冬,目前看起來如下所示:
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,
)
},
當(dāng)前exists
的調(diào)用已經(jīng)確保發(fā)送請求的User
被設(shè)置為要發(fā)布的Post
的作者(author
)黍聂。所以躺苦,你實際上也不必做任何改變,而且這項要求已經(jīng)被滿足了产还。
deletePost
deletePost
突變有以下要求:
只有
Post
的作者(author
)或ADMIN
用戶才能刪除它匹厘。
當(dāng)前的解析器在src/resolvers/Mutation/post.js
中實現(xiàn),如下所示:
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
調(diào)用已確保請求User
是要刪除的Post
的作者author
愈诚。但是,如果該用戶是ADMIN
牛隅,則該帖子仍應(yīng)被刪除炕柔。
(7) 調(diào)整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 } })
},
權(quán)限測試
您可以在GraphQL Playground中獲得權(quán)限媒佣。以下是流程:
- 在Playground中匕累,使用
signup
突變創(chuàng)建一個新User
,并選擇(selection)中指定令牌(token
)默伍,以便服務(wù)端返回該令牌(token
)(如果您以前已經(jīng)創(chuàng)建了用戶欢嘿,則當(dāng)然也可以使用login
突變) - 將服務(wù)端返回中的令牌保存起來,并將其設(shè)置為Playground中的
Authorization
標(biāo)頭(header
)(您將學(xué)習(xí)如何做到這一點) - 所有后續(xù)請求現(xiàn)在都代表該用戶(
User
)發(fā)送
1.創(chuàng)建新User
你首先需要打開一個GraphQL Playground也糊,但在這之前炼蹦,你需要啟動服務(wù)!
(8) 在permissions-example
目錄中狸剃,在終端中運行以下命令:
yarn start
服務(wù)現(xiàn)在運行在 http://localhost:4000.
(9) 瀏覽器中打開 http://localhost:4000
(10) 在默認(rèn)(default)的Playground中app部分框弛,發(fā)送以下突變:
mutation {
signup(
email: "sarah@graph.cool"
password: "graphql"
name: "Sarah"
) {
token
}
}
(11) 復(fù)制令牌(token
)并將其設(shè)置為Playground左下角的Authorization
標(biāo)頭(header)。您需要將標(biāo)頭(header)設(shè)置為JSON捕捂,如下所示(請注意,您需要將TOKEN占位符替換為從signup
突變返回的身份驗證令牌token):
{
"Authorization": "__TOKEN__"
}
從現(xiàn)在開始斗搞,通過Playground發(fā)送的所有請求均代表您剛剛創(chuàng)建的用戶(User
)發(fā)送指攒。
配備這些知識,您現(xiàn)在可以利用可用的查詢和變異來驗證權(quán)限規(guī)則是否有效僻焚。
例如允悦,您可以通過以下流程:
- 代表
Sarah
(您剛創(chuàng)建的用戶)創(chuàng)建一個包含createDraft
突變的新草稿。 - 使用
signup
突變創(chuàng)建另一個用戶并為他們請求一個令牌(token
)虑啤。 - 使用新的身份驗證令牌嘗試發(fā)布Sarah的草稿隙弛。應(yīng)該會返回以下錯誤:
Post not found or you don't have access rights to delete it.
。