Skip to main content

stateset_authz/
lib.rs

1#![deny(unsafe_code)]
2#![cfg_attr(not(test), deny(clippy::unwrap_used))]
3#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc(
6    html_logo_url = "https://raw.githubusercontent.com/stateset/stateset-icommerce/main/assets/stateset.png",
7    html_favicon_url = "https://raw.githubusercontent.com/stateset/stateset-icommerce/main/assets/favicon.ico",
8    issue_tracker_base_url = "https://github.com/stateset/stateset-icommerce/issues/"
9)]
10
11//! # StateSet Authorization
12//!
13//! IO-free, framework-agnostic authorization engine for the StateSet iCommerce
14//! platform. This crate is a Rust port of the permission model in
15//! `cli/src/permissions.js`, designed to be reusable across runtimes (CLI,
16//! server, WASM, NAPI bindings).
17//!
18//! ## Features
19//!
20//! - **Permission levels** — `None` < `Read` < `Preview` < `Write` < `Delete` < `Admin`
21//! - **Role-based access control** — built-in roles (`admin`, `operator`, `viewer`, `none`)
22//!   plus a [`RoleBuilder`] for custom roles
23//! - **Window-based rate limiting** — per-actor, per-resource request tracking
24//! - **Audit logging** — in-memory ring buffer with filtering by actor, resource, time
25//! - **Sensitive field redaction** — recursive JSON traversal + partial string masking
26//! - **Access decisions** — `Allowed`, `Denied`, `RequiresApproval` with reasons
27//! - **Zero IO** — no filesystem, network, or database access; bring your own persistence
28//!
29//! ## Quick Start
30//!
31//! ```rust
32//! use stateset_authz::{AuthzEngineBuilder, Role, Action, Resource, RateLimitRule};
33//! use std::time::Duration;
34//!
35//! let mut engine = AuthzEngineBuilder::new()
36//!     .add_role(Role::admin())
37//!     .add_role(Role::viewer())
38//!     .assign_role("alice", "admin")
39//!     .assign_role("bob", "viewer")
40//!     .rate_limit_rule(RateLimitRule::new("orders", 100, Duration::from_secs(60)))
41//!     .build();
42//!
43//! // Alice (admin) can create orders
44//! let decision = engine.authorize("alice", &Resource::new("orders"), &Action::Create);
45//! assert!(decision.is_allowed());
46//!
47//! // Bob (viewer) cannot create orders
48//! let decision = engine.authorize("bob", &Resource::new("orders"), &Action::Create);
49//! assert!(decision.is_denied());
50//!
51//! // Every decision is recorded in the audit log
52//! assert_eq!(engine.audit_log().len(), 2);
53//! ```
54//!
55//! ## Custom Roles
56//!
57//! ```rust
58//! use stateset_authz::{RoleBuilder, PermissionLevel, Action};
59//!
60//! let order_manager = RoleBuilder::new("order-manager")
61//!     .default_level(PermissionLevel::Read)
62//!     .allow("orders", PermissionLevel::Admin)
63//!     .allow("customers", PermissionLevel::Write)
64//!     .build();
65//!
66//! assert!(order_manager.check("orders", &Action::Delete).is_allowed());
67//! assert!(order_manager.check("customers", &Action::Create).is_allowed());
68//! assert!(order_manager.check("inventory", &Action::Create).is_denied());
69//! ```
70//!
71//! ## Redaction
72//!
73//! ```rust
74//! use stateset_authz::{RedactionConfig, redact_value, redact_string};
75//! use serde_json::json;
76//!
77//! let config = RedactionConfig::default();
78//! let mut data = json!({ "name": "Alice", "password": "s3cr3t", "token": "abc" });
79//! redact_value(&mut data, &config);
80//!
81//! assert_eq!(data["name"], "Alice");
82//! assert_eq!(data["password"], "[REDACTED]");
83//! assert_eq!(data["token"], "[REDACTED]");
84//!
85//! assert_eq!(redact_string("secret123"), "sec***123");
86//! ```
87
88mod audit;
89mod decision;
90mod engine;
91mod error;
92mod permission;
93mod rate_limit;
94mod redaction;
95mod resource;
96mod role;
97
98pub use audit::*;
99pub use decision::*;
100pub use engine::*;
101pub use error::*;
102pub use permission::*;
103pub use rate_limit::*;
104pub use redaction::*;
105pub use resource::*;
106pub use role::*;
107
108/// Compiles the code examples in `README.md` as doctests, so the crates.io
109/// landing page can never drift from the real API.
110#[cfg(doctest)]
111#[doc = include_str!("../README.md")]
112struct ReadmeDoctests;