Skip to main content

ferrohealth_smart/
lib.rs

1// SPDX-FileCopyrightText: Cadasto B.V.
2// SPDX-License-Identifier: BUSL-1.1
3
4//! The shared SMART App Launch 2.x and OAuth 2.0 resource-server crate of the
5//! FerroHEALTH products.
6//!
7//! This crate is a library, never an authorization server: it issues no token
8//! and holds no key that signs one. Each deployment points it at its own
9//! standards-compliant authorization server, and the crate does four things
10//! every product needs:
11//!
12//! - [`resource`]: the resource-server role. The issuer's metadata is read at
13//!   the RFC 8414 §3 location, with `OpenID` Connect Discovery 1.0 §4 as the
14//!   fallback ([`metadata`]), its key set is read and read again on a
15//!   rotation, and every JWT access token is validated under RFC 9068 (the
16//!   signature with an asymmetric algorithm only, `iss`, `aud`, `exp` and
17//!   `nbf` within a clock skew). A refused token is one generic `401` that
18//!   names no claim ([`refusal`]).
19//! - [`scope`]: the SMART App Launch 2.x scopes, `patient/`, `user/` and
20//!   `system/` with the `cruds` permissions, and the patient compartment the
21//!   token's launch context names; a missing permission is a `403` that names
22//!   no scope. Behind the `openehr` feature, `openehr` enforces the SMART
23//!   on openEHR grammar beside it, parsed by `openehr::smart_scopes`: the
24//!   `template-`, `composition-` and `aql-` families with their patterns,
25//!   and the `ehrId` of the launch context. The product's route table
26//!   declares which grammar each route is checked against.
27//! - [`basic`] and [`client`]: machine-to-machine authentication in exactly
28//!   two modes, HTTP Basic (RFC 7617, verified against Argon2id hashes and
29//!   sent outgoing) and the OAuth 2.0 client credentials grant (RFC 6749 §4.4,
30//!   a token client with a cache and one renewal on a `401`, authenticating
31//!   with a secret or a `private_key_jwt` assertion). A static bearer token
32//!   or an API key is not a mode.
33//! - [`kind`] and [`profile`]: who stands behind a token. Each issuer
34//!   declares its kind and a profile, a preset of what its tokens look like
35//!   with any key set over it, and every valid token is classified as a
36//!   patient, a system, a delegated person, a representative, a person or a
37//!   guest, or refused naming the step it matched none of.
38//! - [`party`]: the party a person's login belongs to, looked up on every
39//!   request through a source the product supplies, with one typed outcome.
40//!   Where a token references its groups in place of carrying them, the
41//!   [`idp`] adapter its issuer's profile names reads them, each adapter
42//!   behind its own feature.
43//! - [`assurance`]: the assurance level of the authentication behind a
44//!   token, with the RFC 9470 step-up refusal below a minimum.
45//! - [`sender`]: sender-constrained tokens, bound by a `DPoP` proof (RFC 9449)
46//!   or by the client certificate of a mutual-TLS connection (RFC 8705 §3),
47//!   checked per issuer, with the proof replay store the product supplies.
48//! - [`roles`]: the roles a caller holds, read from the claims RFC 9068
49//!   §2.2.3.1 names or granted to a Basic user, which a route requirement
50//!   checks beside its scopes.
51//! - [`audit`]: one audit line per request, which carries the principal, the
52//!   client, the scheme, the issuer, the method, the route, what was
53//!   addressed, the interaction, the status and the duration, and never the
54//!   token or any content.
55//!
56//! [`config`] holds the `[auth]` and `[<lane>.credentials]` sections every
57//! product embeds. The `axum` feature adds `guard`, the `tower::Layer` that
58//! runs the credential check, the scope check and the audit line in front of
59//! every route, and `extract`, the extractor of the caller the layer
60//! admitted. The core stays framework-neutral. The HTTP fetchers (the
61//! issuer's metadata, the key set over its `jwks_uri`, and the client
62//! credentials token client) sit behind the default `fetch` feature; without
63//! it the crate links no HTTP client and reads a key set from a file. The
64//! `oidc` feature adds `oidc`, the OpenID Connect relying-party client an
65//! operator console signs its operator in with.
66//!
67//! The `openehr-scopes` feature builds `openehr::smart_scopes`, the SMART on
68//! openEHR scope grammar, which depends on nothing. For the browser
69//! (`wasm32-unknown-unknown`) the grammar is the whole crate: the resource
70//! server is left out with its dependencies, so a browser client parses
71//! scopes with the parser the resource server enforces them with.
72
73#![doc(test(attr(deny(warnings))))]
74
75#[cfg(resource_server)]
76pub mod assurance;
77#[cfg(resource_server)]
78pub mod audit;
79#[cfg(resource_server)]
80pub mod basic;
81#[cfg(resource_server)]
82pub mod client;
83#[cfg(resource_server)]
84pub mod config;
85#[cfg(all(feature = "axum", resource_server))]
86pub mod extract;
87#[cfg(resource_server)]
88pub mod federation;
89#[cfg(all(feature = "axum", resource_server))]
90pub mod guard;
91#[cfg(resource_server)]
92pub mod idp;
93#[cfg(resource_server)]
94mod jws;
95#[cfg(resource_server)]
96pub mod kind;
97#[cfg(resource_server)]
98pub mod metadata;
99#[cfg(all(feature = "oidc", resource_server))]
100pub mod oidc;
101#[cfg(feature = "openehr-scopes")]
102pub mod openehr;
103#[cfg(resource_server)]
104pub mod party;
105#[cfg(resource_server)]
106pub mod profile;
107#[cfg(resource_server)]
108pub mod refusal;
109#[cfg(resource_server)]
110pub mod resource;
111#[cfg(resource_server)]
112pub mod roles;
113#[cfg(resource_server)]
114pub mod scope;
115#[cfg(resource_server)]
116pub mod sender;