1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//! # laravel-iam
//!
//! A thin, **fail-closed** Rust client for the [Laravel IAM](https://github.com/padosoft)
//! authorization server. It speaks the canonical decision protocol
//! (`POST {base_url}/decisions/check`) and verifies OIDC tokens against the server's JWKS —
//! mirroring the production PHP client's wire contract exactly, in idiomatic async Rust.
//!
//! There is **no policy logic on the client**: every decision is the server's. The client only
//! transports the question and the answer, and refuses to invent an "allow".
//!
//! ## Fail-closed guarantee
//!
//! A network error, timeout, 5xx, 4xx, malformed body or unverifiable token always becomes a
//! **deny** — never an allow. Operations return `Result<_, IamError>`; the [`ResultExt::is_allowed`]
//! helper collapses any error into `false` so a gate cannot accidentally open:
//!
//! ```no_run
//! use laravel_iam::{IamClient, DecisionQuery, Subject, ResultExt};
//! use serde_json::json;
//!
//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
//! let iam = IamClient::builder()
//! .base_url("https://iam.example.com/api/iam/v1")
//! .token(std::env::var("IAM_SERVICE_TOKEN")?)
//! .build()?;
//!
//! let decision = iam.check(DecisionQuery {
//! subject: Subject::user("usr_123"),
//! application: Some("warehouse".into()),
//! permission: "stock.adjust".into(),
//! resource: Some("wh_milan".into()),
//! context: json!({ "amount": 300 }),
//! ..Default::default()
//! }).await;
//!
//! // `decision` is `Result<Decision, IamError>`; on ANY error this is `false`.
//! if !decision.is_allowed() {
//! // deny — fail-closed
//! }
//! # Ok(())
//! # }
//! ```
//!
//! ## Token verification
//!
//! [`IamClient::verify_token`] checks an ES256 signature against the cached JWKS plus the
//! `iss`/`aud`/`exp` claims. Configure the expected issuer and audience on the builder.
//!
//! ## Features
//!
//! - `blocking` — adds a synchronous [`blocking::IamClient`] with identical semantics.
pub use IamClient;
pub use IamClientBuilder;
pub use IamError;
pub use ;
/// Fail-closed extension for `Result<Decision, IamError>`.
///
/// Lets a caller write `if iam.check(q).await.is_allowed()` and be certain that **every**
/// error path — and every pending step-up — evaluates to `false`.