Skip to main content

Crate laye

Crate laye 

Source
Expand description

laye is a framework-agnostic role- and permission-based access control (RBAC) library. Build composable AccessPolicy rules, implement Principal on your auth type, and wire the provided middleware into actix-web or axum (tower). Authentication — decoding tokens, querying the database — is entirely outside laye’s scope.

§Feature flags

FeatureWhat it adds
actix-webactix module: PolicyMiddlewareFactory, AuthPrincipal, MaybeAuthPrincipal
towertower_middleware module: AccessControlLayer

§How it works

  1. Implement Principal on your auth info struct (decoded JWT payload, session data, etc.).
  2. Build an AccessPolicy with AccessPolicy::require_all (AND) or AccessPolicy::require_any (OR), chaining AccessRules and nested sub-policies.
  3. Insert your principal into request extensions from your own upstream auth middleware — before laye runs:
    • actix-web: req.extensions_mut().insert(my_user)
    • axum / tower: req.extensions_mut().insert(my_user)
  4. Apply a laye middleware / layer. It reads P from extensions and calls AccessPolicy::check. If no principal is found the request is rejected with 401 Unauthorized; if the principal fails the policy it is rejected with 403 Forbidden.

§Quick start

Implement Principal:

use laye::Principal;

#[derive(Clone)]
struct MyUser {
    roles: Vec<String>,
    permissions: Vec<String>,
    authenticated: bool,
}

impl Principal for MyUser {
    fn roles(&self) -> &[String] { &self.roles }
    fn permissions(&self) -> &[String] { &self.permissions }
    fn is_authenticated(&self) -> bool { self.authenticated }
}

Build and evaluate a policy:

let editor = MyUser { roles: vec!["editor".into()], permissions: vec![], authenticated: true };
let viewer = MyUser { roles: vec!["viewer".into()], permissions: vec![], authenticated: true };

// Require authenticated AND (role "admin" OR role "editor")
let policy = AccessPolicy::require_all()
    .add_rule(AccessRule::Authenticated)
    .add_policy(
        AccessPolicy::require_any()
            .add_rule(AccessRule::Role("admin".into()))
            .add_rule(AccessRule::Role("editor".into())),
    );

assert_eq!(policy.check(Some(&editor)), LayeCheckResult::Authorized);
assert_eq!(policy.check(Some(&viewer)), LayeCheckResult::Forbidden);
assert_eq!(policy.check(None),          LayeCheckResult::Unauthorized);

Permission-based rules work the same way:

let writer = MyUser {
    roles: vec![],
    permissions: vec!["posts:write".into()],
    authenticated: true,
};

let policy = AccessPolicy::require_all()
    .add_rule(AccessRule::Authenticated)
    .add_rule(AccessRule::Permission("posts:write".into()));

assert_eq!(policy.check(Some(&writer)), LayeCheckResult::Authorized);

Guest-only route (reject already-authenticated users):

let guest_policy = AccessPolicy::require_all().add_rule(AccessRule::Guest);

assert_eq!(guest_policy.check(None),           LayeCheckResult::Authorized);
assert_eq!(guest_policy.check(Some(&editor)),  LayeCheckResult::Forbidden);

§Framework integrations

See laye::actix for actix-web middleware and extractors.

See laye::tower_middleware for the tower / axum layer.

Re-exports§

pub use policy::AccessPolicy;
pub use policy::AccessRule;
pub use principal::Principal;
pub use result::LayeCheckResult;

Modules§

actixactix-web
actix-web middleware and extractors for laye.
policy
Policy types: AccessPolicy, AccessRule, and CustomFn. The AccessPolicy builder and AccessRule enum.
principal
The Principal trait. The Principal trait that callers implement on their auth type.
result
The LayeCheckResult type. The LayeCheckResult type returned by every policy evaluation.
tower_middlewaretower
tower / axum middleware for laye.