Caslという認可ライブラリについて調べる
とりあえず日本語で書かれた先行事例を調べる
CASL クラッシュコース
CASL はユーザーの"ability"レベルで動作します。つまり、ユーザーがアプリケーション内で実際に何をできるかを表します。"ability"自体は 4 つのパラメーター(最後の 3 つはオプション)に依存します:
- User Action
アプリケーション内でユーザーが実際に何をすることができるかを記述します。ユーザーアクションは、ビジネスロジックに依存する単語(通常は動詞)です(例:prolong
、read
)。多くの場合、CRUDの各単語(create
、read
、update
、delete
)のリストになります。 - Subject
ユーザーアクションをチェックしたい対象または対象タイプです。通常はビジネス(またはドメイン)エンティティ名です(例:Subscription
、BlogPost
、User
)。 - Conditions
ユーザーアクションを一致する対象に制限するオブジェクトまたは関数です。これは、ユーザーによって作成されたリソースへの許可を与える必要がある場合に便利です(例:ユーザに自分のBlogPost
を更新・削除する権限を与える)。 - Fields
ユーザーアクションをマッチした対象のフィールドだけに制限用に使用できます(例:モデレーターはBlogPost
のhidden
フィールドを更新することができるが、description
やtitle
を更新することはできない)。
CASL を使用すると、通常のルールと反転したルールを使用して"ability"を記述できます。どのようにするのか見てみましょう。
**注意:**以下のすべての例は TypeScript で書かれていますが、CASL は ES6+および Nodejs 環境でも同様に使われています。
1. "ability"の定義
以下のようなブログウェブサイトのAbility
を定義しましょう。ここでは、訪問者が:
- ブログ投稿を読むことができる
- 自身の投稿を管理する(つまり、何でも)できる
- 投稿が 1 日以上前に作成された場合、削除することはできない
import { AbilityBuilder, createMongoAbility } from '@casl/ability'
import { User } from '../models'; // アプリ固有のインターフェース
/**
* @param user ログインしているユーザーの詳細を含む: id, 名前, メール, etc
*/
function defineAbilitiesFor(user: User) {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// ブログ投稿を読むことができる
can('read', 'BlogPost');
// 自分自身の投稿を管理する(つまり、何でも)できる
can('manage', 'BlogPost', { author: user.id });
// 投稿が1日以上前に作成された場合、削除することはできない
cannot('delete', 'BlogPost', {
createdAt: { $lt: Date.now() - 24 * 60 * 60 * 1000 }
});
return build();
});
ビジネス要件がどれだけ簡単にCASLのルールに変換されたか分かりましたか?
注意: 対象タイプとして文字列ではなくクラスを使用することもあります(例 can('read', BlogPost)
)
そして、はい、Ability
クラスでは、条件を定義するために MongoDB 演算子を使用することができます。MongoDB を知らなくても心配しないでください、これは必須ではなく ["ability"定義][define-abilities] で詳しく説明されています。
2. "ability"のチェック
その後、Ability
インスタンスの can
と cannot
メソッドを使って"ability"をチェックすることができます。
// 上記と同じファイルに
import { ForbiddenError } from "@casl/ability";
const user = getLoggedInUser(); // アプリ固有の関数
const ability = defineAbilitiesFor(user);
class BlogPost {
// ビジネスエンティティ
constructor(props) {
Object.assign(this, props);
}
}
// "ability"が少なくとも1つの投稿を読むために許可されている場合はtrue
ability.can("read", "BlogPost");
// 下記も同じ意味です
ability.can("read", BlogPost);
// ユーザーがブログ投稿の著者である場合はtrue
ability.can("manage", new BlogPost({ author: user.id }));
const ONE_DAY = 24 * 60 * 60 * 1000;
const postCreatedNow = new BlogPost({ createdAt: new Date() });
const postCreatedAWeekAgo = new BlogPost({
createdAt: new Date(Date.now() - 7 * ONE_DAY),
});
// 投稿の作成から1日以内の場合は削除可能
ability.can("delete", postCreatedNow); // true
ability.can("delete", postCreatedAWeekAgo); // false
// 指定の"ability"がない場合、エラーをスローすることもできます
ForbiddenError.from(ability).throwUnlessCan("delete", postCreatedAWeekAgo);
もちろん、オブジェクトのパーミッションをチェックするために、クラスのインスタンスだけを使用する必要はありません。詳しい説明についてはIntroduction を参照してください。
3. データベースとの統合
CASL には [@casl/mongoose] という補足的なパッケージがあり、MongoDB および [mongoose] との簡単な統合を提供します。
import { accessibleRecordsPlugin } from "@casl/mongoose";
import mongoose from "mongoose";
mongoose.plugin(accessibleRecordsPlugin);
const user = getUserLoggedInUser(); // アプリ固有の関数
const ability = defineAbilitiesFor(user);
const BlogPost = mongoose.model(
"BlogPost",
mongoose.Schema({
title: String,
author: mongoose.Types.ObjectId,
content: String,
createdAt: Date,
hidden: { type: Boolean, default: false },
})
);
// 戻り値はmongoose Queryなので、他の条件とチェインさせることができます
const posts = await BlogPost.accessibleBy(ability).where({ hidden: false });
// 既存のクエリに対してもそれを呼び出して権限を強制することができます
const hiddenPosts = await BlogPost.find({ hidden: true }).accessibleBy(ability);
// 2番目のパラメータとしてアクションを渡すこともできます。デフォルトではアクションは "read"です
const updatablePosts = await BlogPost.accessibleBy(ability, "update");
詳細については Database integration をご覧ください。
4. 高度な使い方
**CASL は段階的に導入可能です。**つまり、プロジェクトを単純なクレーム(またはアクション)ベースの認可で始めることができ、アプリケーションの機能が進化するときにそれを進化させることができます。
CASL は組み合わせ可能です。 つまり、代替の条件マッチング(例えば、joi、ajv、または純粋な関数)およびフィールドマッチング(例えば、フィールドで addresses.*.street
や addresses[0].street
などの代替構文をサポートする)ロジックを実装できます。
詳細については 高度な使い方 をご覧ください。
5. 例
例を探していますか? CASL examples リポジトリをご覧ください。
- ChatGPTベースに適宜修正した