I'm afraid I can't let you do that, Dave.
A simple authorisation library for effect-ts.
sorry-dave.mp4
- 🔒 Type-safe
- 📋 standard-schema compliant
- 🚀 Easy to use
- 📝 Declarative
- ⚡ Fully
Effect
compatible
# npm
npm install @betalyra/sorry-dave
# pnpm
pnpm add @betalyra/sorry-dave
# yarn
yarn add @betalyra/sorry-dave
sorry-dave consists of three building blocks:
- Schema registry: Maps your domain objects to resource identifiers
- Capabilities: Defines what your entity (usually the user) is allowed to do and under what conditions
- Check: A check used in your program to validate that your entity (user) has sufficient permissions
Let's go through them:
Declare a schema for your capabilities using any standard-schema compliant library, e.g. zod.
// Define your domain model
const Article = z.object({
article: z.literal("article"),
authorId: z.string(),
});
const Blog = z.object({ blog: z.literal("blog"), authorId: z.string() });
const User = z.object({ id: z.string() });
type User = z.infer<typeof User>;
Next, register your schemas. Keys must be lowercase in the form of action-resource.
const registry = register({
"read-article": Article,
"read-blog": Blog,
"write-article": Article,
"write-blog": Blog,
});
Now that we have our registry up and running, let's define a set of capabilities that your entity (the user) has.
const capabilities = (user: User) =>
define(registry)(function* () {
yield* can("read-article"); // Anyone can read an article
yield* can("read-blog"); // Anyone can read a blog
// Add a condition to the capability
yield* can("write-article", (input) => input.authorId === user.id); // Only the author can write an article
yield* can("write-blog", (input) => Effect.sync(() => input.authorId === user.id)); // You can also use Effects in conditions
});
Finally we can check within our program whether the entity is allowed to perform certain actions:
Valid case
const user = { id: "1" };
const article = { article: "article" as const, authorId: "1" };
yield* check(capabilities(user))(function* () {
yield* allowed("read-article", article);
yield* allowed("write-article", article);
});
Invalid case
const user = { id: "1" };
const article = { article: "article" as const, authorId: "2" }; // The authorId does not match the user's id
// Will fail with a Denied error
yield* check(capabilities(user))(function* () {
yield* allowed("read-article", article);
yield* allowed("write-article", article);
});
You can easily register CRUD actions using the crud
helper:
const registry = register({
...crud("blog", Blog),
...crud("article", Article),
});
This library was heavily inspired by CASL.