現在、ほとんどすべてのアプリケーションにはアクセス権の概念があり、さまざまな機能をさまざまなユーザーグループ(たとえば、admin、member、subscriberなど)に提供しています。 これらのグループは、一般に「ロール」と呼ばれます。
私自身の経験から、ほとんどのアプリケーションのアクセス権のロジックはロールを中心に構築されていると言います(検証は次のとおりです:ユーザーがこのロールを持っている場合、彼は何かをすることができます)。最終的に、維持するのが難しい多くの複雑なチェックを備えた巨大なシステムがあります この問題はCASLで解決できます。
CASLはJavaScriptでの承認のためのライブラリであり、ユーザーがシステムで実行できることではなく、ユーザーが持っている役割について考えることができます(チェックは次のようになります。ユーザーがこの機能を持っている場合、実行できます)。 たとえば、ブログアプリケーションでは、ユーザーは記事やコメントを作成、編集、削除、表示できます。 これらの機能を、匿名ユーザー(システムで識別されないユーザー)とライター(システムで識別されるユーザー)の2つのユーザーグループで共有しましょう。
匿名ユーザーは記事とコメントのみを読むことができます。 ライターは同じことを行うことができ、さらに記事とコメントを管理できます(この場合、「管理」とは作成、読み取り、更新、削除を意味します)。 CASLを使用すると、次のように記述できます。
import { AbilityBuilder } from 'casl' const user = whateverLogicToGetUser() const ability = AbilityBuidler.define(can => { can('read', ['Post', 'Comment']) if (user.isLoggedIn) { can('create', 'Post') can('manage', ['Post', 'Comment'], { authorId: user.id }) } })
したがって、ユーザーができることをロールに基づいて決定できるだけでなく、他の基準にも基づいて決定できます。 たとえば、評判に基づいて他のコメントやメッセージをモデレートすることをユーザーに許可し、18歳であることを確認したユーザーのみにコンテンツの表示を許可することができます。 CASLを使用すると、これらすべてを1か所で説明できます。
さらに、MongoDBのクエリ言語の一部の演算子を使用して、条件を決定できます。 たとえば、コメントがない場合、削除する記事を指定できます。
can('delete', 'Post', { 'comments.0': { $exists: false } })
可能性を確認する
Ability
インスタンスには、アクセス許可を確認できる3つのメソッドがあります。
import { ForbiddenError } from 'casl' ability.can('update', 'Post') ability.cannot('update', 'Post') try { ability.throwUnlessCan('update', 'Post') } catch (error) { console.log(error instanceof Error) // true console.log(error instanceof ForbiddenError) // true }
最初のメソッドはfalse
を返し、2番目はtrue
を返し、3番目は匿名ユーザーに対してForbiddenError
をスローします。これは、ユーザーが記事を更新する権利を持っていないためです。 2番目の引数として、これらのメソッドはクラスのインスタンスを取ることができます:
const post = new Post({ title: 'What is CASL?' }) ability.can('read', post)
この場合、 can ('read', post)
はtrue
返しtrue
。これは、ユーザーがすべての記事を読むことができると判断したためです。 オブジェクトのタイプは、 constructor.name
基づいて計算さconstructor.name
ます。 Post
クラスで静的プロパティmodelName
を作成することでオーバーライドできます。これは、アセンブリの生産のために関数名が縮小される場合に必要になることがあります。 オブジェクトのタイプを判別する関数を作成し、それをオプションとしてAbility
コンストラクターに渡すこともできます。
import { Ability } from 'casl' function subjectName(subject) { // custom logic to detect subject name, should return string or undefined } const ability = new Ability([], { subjectName })
ユーザーが別のユーザーの記事を更新しようとした場合を確認しましょう(別の著者の識別子をmyId
として、現在のユーザーの識別子をmyId
としてmyId
)。
const post = new Post({ title: 'What is CASL?', authorId: 'anotherId' }) ability.can('update', post)
この場合、 can('update', post)
はfalse
返しfalse
。これは、ユーザーが自分の記事のみを更新できると判断したためです。 もちろん、あなたがあなた自身の記事で同じことをチェックすれば、私たちはtrue
になりtrue
。 アクセスチェックの詳細については、公式ドキュメントの「 チェック機能」セクションを参照してください 。
データベース統合
CASLは、記述された権限をデータベースクエリに変換できる機能を提供します。 したがって、ユーザーがアクセスできるデータベースからすべてのレコードを取得するのは非常に簡単です。 現在、ライブラリはMongoDBのみをサポートし、他のクエリ言語との統合を記述するためのツールを提供します。
許可をMongoクエリに変換するには、 toMongoQuery
関数があります。
import { toMongoQuery } from 'casl' const query = toMongoQuery(ability.rulesFor('read', 'Post'))
この場合、ユーザーはすべての記事を読むことができるため、 query
は空のオブジェクトになります。 更新操作の出力を確認してみましょう。
// { $or: [{ authorId: 'myId' }] } const query = toMongoQuery(ability.rulesFor('update', 'Post'))
現在、 query
は、自分が作成したレコードのみを返すquery
が含まれています。 通常のルールはすべて論理ORチェーンを通過するため、リクエストの結果として$or
演算子が表示されます。
CASLは、 accessibleBy
メソッドをモデルに追加するmongooseプラグインも提供します。 このメソッドは、 toMongoQuery
関数を呼び出し、結果をfind
toMongoQuery
-aメソッドに渡しfind
。
const { mongoosePlugin, AbilityBuilder } = require('casl') const mongoose = require('mongoose') mongoose.plugin(mongoosePlugin) const Post = mongoose.model('Post', mongoose.Schema({ title: String, author: String, content: String, createdAt: Date })) // by default it asks for `read` rules and returns mongoose Query, so you can chain it Post.accessibleBy(ability).where({ createdAt: { $gt: Date.now() - 24 * 3600 } }) // also you can call it on existing query to enforce visibility. // In this case it returns empty array because rules does not allow to read Posts of `someoneelse` author Post.find({ author: 'someoneelse' }).accessibleBy(ability, 'update').exec()
デフォルトでは、 accessibleBy
はread
権限に基づいてリクエストを作成しread
。 別のアクションのクエリを作成するには、単純に2番目の引数を渡します。 詳細については、 データベース統合セクションを参照してください。
そして最後に
CASLは純粋なES6で記述されているため、APIとUI側の両方を認証するために使用できます。 さらに、UIはAPIからすべてのアクセス権を要求し、それらを使用してページ上のボタンまたはセクション全体を表示または非表示にすることができます。