權(quán)限

本文屬使用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,因為prismanode-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-eu1prisma-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ī)則是否有效僻焚。

例如允悦,您可以通過以下流程:

  1. 代表Sarah(您剛創(chuàng)建的用戶)創(chuàng)建一個包含createDraft突變的新草稿。
  2. 使用signup突變創(chuàng)建另一個用戶并為他們請求一個令牌(token)虑啤。
  3. 使用新的身份驗證令牌嘗試發(fā)布Sarah的草稿隙弛。應(yīng)該會返回以下錯誤:Post not found or you don't have access rights to delete it.
權(quán)限不足錯誤信息
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狞山,一起剝皮案震驚了整個濱河市全闷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萍启,老刑警劉巖总珠,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屏鳍,死亡現(xiàn)場離奇詭異,居然都是意外死亡局服,警方通過查閱死者的電腦和手機(jī)钓瞭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淫奔,“玉大人山涡,你說我怎么就攤上這事∷羟ǎ” “怎么了鸭丛?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長媒惕。 經(jīng)常有香客問我系吩,道長,這世上最難降的妖魔是什么妒蔚? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任穿挨,我火速辦了婚禮,結(jié)果婚禮上肴盏,老公的妹妹穿的比我還像新娘科盛。我一直安慰自己,他們只是感情好菜皂,可當(dāng)我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布贞绵。 她就那樣靜靜地躺著,像睡著了一般恍飘。 火紅的嫁衣襯著肌膚如雪榨崩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天章母,我揣著相機(jī)與錄音母蛛,去河邊找鬼。 笑死乳怎,一個胖子當(dāng)著我的面吹牛彩郊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚪缀,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼秫逝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了询枚?” 一聲冷哼從身側(cè)響起违帆,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎金蜀,沒想到半個月后前方,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狈醉,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年惠险,在試婚紗的時候發(fā)現(xiàn)自己被綠了苗傅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡班巩,死狀恐怖渣慕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抱慌,我是刑警寧澤逊桦,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站抑进,受9級特大地震影響强经,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寺渗,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一匿情、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧信殊,春花似錦炬称、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鳄乏,卻和暖如春跷车,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背橱野。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工朽缴, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人仲吏。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像蝌焚,于是被迫代替她去往敵國和親裹唆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容