Introduction to handling permissions

Just a guy who loves to write code and watch anime.
Search for a command to run...

Just a guy who loves to write code and watch anime.
No comments yet. Be the first to comment.
Qualities that make outstanding builders.

Bipedal vs quadrupedal: how many legs This is about the number of legs the creature walks on. Bipedal. Two legs. Humans, ostriches, T-rex, kangaroos, most fantasy humanoids. Quadrupedal. Four legs. Do

Intro You hear "lerp" and "smoothstep" everywhere in game dev. They sound like math jargon. They're not. Both are small tools that do the same job: smoothly move from one value to another. The problem

What is Kinematics Kinematics is the math field. It's the study of how things move without worrying about forces (which would be dynamics). FK and IK are the two branches: forward kinematics and inver

Intro Textures are usually the biggest cost in a 3D scene. Memory, bandwidth, and load time all get eaten by them. Resizing them is the obvious lever. There's more. This post is about the less obvious

At first, many of us begin with a basic role system:
type User = {
id: string;
role: "admin" | "editor" | "user";
};
function canDeleteArticle(user: User) {
return user.role === "admin";
}
This setup is fine for basic needs. Admins can do everything, editors can edit, and users can view. It's simple and clear.
But as needs change, you might ask, "What if editors should delete their own articles?" Now our simple function changes to:
function canDeleteArticle(user: User, article: Article) {
return (
user.role === "admin" ||
(user.role === "editor" && article.authorId === user.id)
);
}
These checks start popping up all over your code. With each new requirement, more conditions are added, making the code harder to maintain and understand.
Instead of spreading permission checks throughout the code, we can centralize them using RBAC (Role-Based Access Control):
type Permission = "create:articles" | "edit:articles" | "delete:articles";
const ROLES = {
admin: ["create:articles", "edit:articles", "delete:articles"],
editor: ["create:articles", "edit:articles"],
user: ["create:articles"],
} as const;
function hasPermission(user: User, permission: Permission) {
return ROLES[user.role].includes(permission);
}
Better! Our permissions are now centralized and clear. Want to know what an editor can do? Just check the ROLES object.
However, RBAC has its limits. For example, the rule "editors can delete their own articles" can't be captured with simple permission strings.
This is where ABAC shines. Instead of just checking roles and permissions, we can check any attribute of the user, resource, or environment:
type User = {
id: string;
role: "admin" | "editor" | "user";
department: string;
};
type Article = {
id: string;
authorId: string;
department: string;
status: "draft" | "published";
};
const permissions = {
admin: {
articles: {
delete: true, // Admins can delete any article
edit: true,
},
},
editor: {
articles: {
delete: (user: User, article: Article) =>
article.authorId === user.id || // Their own articles
article.department === user.department, // Their department's articles
edit: (user: User, article: Article) =>
article.status === "draft" && // Only drafts
(article.authorId === user.id ||
article.department === user.department),
},
},
};
function can(user: User, action: string, resource: string, data?: any) {
const permission = permissions[user.role]?.[resource]?.[action];
if (typeof permission === "boolean") return permission;
if (typeof permission === "function") return permission(user, data);
return false;
}
Now we can set up complex rules like:
Editors can delete their own articles.
Editors can edit draft articles in their department.
Admins can do anything.
Access can depend on the time of day, user location, article status, and more.
The great thing about ABAC is its flexibility. You can use any attribute of the user, resource, or environment for permission checks. Plus, all your logic is centralized in one place.