rustlavel_rbac/lib.rs
1//! Roles and permissions — the authorization half of the auth starter kit.
2//!
3//! Modelled on `laravel-permission`, which is the package Laravel developers
4//! actually reach for, and shaped so the same mental model transfers: roles are
5//! named bundles of permissions, users hold roles, and a user can also be given
6//! — or refused — a single permission on their own.
7//!
8//! ```ignore
9//! // main.rs
10//! App::new()
11//! .plugin(Rbac::from_config(db.clone(), &config)?)
12//! .routes(|r| {
13//! r.group("/admin", |r| {
14//! r.middleware(Authenticate::from_config(&config));
15//! r.middleware(Can::permission("admin.access"));
16//! r.get("", dashboard);
17//! });
18//! });
19//! ```
20//!
21//! # The model
22//!
23//! Five tables. `roles` and `permissions` are lists of names; `role_permission`
24//! attaches a permission to a role; `user_role` gives a user a role; and
25//! `user_permission` gives — or refuses — a permission to one user directly,
26//! which is what its `granted` boolean is for.
27//!
28//! Two ways to hold a permission, then: through a role, or in your own name.
29//!
30//! # Precedence
31//!
32//! The question this package exists to answer is "why can this user still do
33//! X?", so the answer is one short list, applied in order:
34//!
35//! 1. **An explicit direct deny beats everything**, including a super role.
36//! 2. A **super role** passes, with no permissions attached.
37//! 3. A **grant** passes — from a role or made directly; the two rank equally,
38//! so a direct grant beats the *absence* of a role grant.
39//! 4. Otherwise, no. Silence is never permission.
40//!
41//! The one that surprises people is the first: a deny outranks `super-admin`.
42//! It has to. A deny is the only entry an administrator writes in order to say
43//! "no, not this one", and a rule that some role can quietly overrule is not a
44//! rule. The trade is that a super role is not unstoppable — which is a
45//! property worth having, not a hole.
46//!
47//! See [`Grants::allows`] for the implementation and the tests that pin it.
48//!
49//! # Wildcards
50//!
51//! A stored permission may end in `.*`, and `*` on its own matches everything.
52//! So a role holding `users.*` satisfies a check for `users.create`. The
53//! matching is a segment-wise prefix comparison and nothing more — no regular
54//! expressions, no glob syntax, no `*` in the middle. An authorization rule
55//! whose meaning cannot be worked out by reading it is worse than one that
56//! cannot express every case. [`permission_matches`] has the details.
57//!
58//! # The super role
59//!
60//! A role named `super-admin` (configure it with `rbac.super_role`) passes
61//! every check without holding a single permission. It is what makes the first
62//! user of a fresh application able to do anything, and it should be treated
63//! with suspicion after that.
64//!
65//! **The risk, plainly: a super role cannot be audited by listing its
66//! permissions, because it has none.** Reading `role_permissions("super-admin")`
67//! returns an empty list, and that empty list is not what the role can do — it
68//! is the opposite. Nothing shows up in `permissions_for(user)` either. If your
69//! compliance story is "here is what each role may do", a super role is a hole
70//! in it, and the fix is to give the role real permissions (`*` is one) and
71//! turn the escape hatch off with `.super_role("")`.
72//!
73//! # Caching
74//!
75//! An authorization check happens on every guarded request, so a check that
76//! queries the database every time is the slowest thing in the application. A
77//! user's resolved grants are cached in memory for 30 seconds
78//! (`rbac.cache_ttl_ms`), and every method that changes what a user is allowed
79//! invalidates it immediately — an administrator removing a role sees it take
80//! effect on the next request, not in half a minute. The TTL is only the
81//! backstop for a change made by *another* process; it is not the mechanism.
82//!
83//! The cache lives in the [`Permissions`] handle, and cloning one shares it.
84//! Register it once, in application state.
85
86pub mod grants;
87pub mod guard;
88pub mod plugin;
89pub mod store;
90pub mod tables;
91
92pub use grants::{Grants, permission_matches};
93pub use guard::{Can, RbacExt};
94pub use plugin::Rbac;
95pub use store::{DEFAULT_CACHE_TTL, DEFAULT_SUPER_ROLE, Named, Permissions};
96pub use tables::{CreateRbacTables, TableNames, create_tables, drop_tables, migrations};
97
98pub use rustlavel_core::{Error, Result};
99
100/// Everything an application usually wants: `use rustlavel_rbac::prelude::*;`
101pub mod prelude {
102 pub use crate::{Can, Grants, Permissions, Rbac, RbacExt, TableNames, migrations};
103}