Expand description
An AAuth Person Server (draft-hardt-oauth-aauth-protocol -11,
three-party) you can run instead of building one.
A Person Server speaks for people when agents want to act for them. This one:
- verifies each agent by its HTTP Message Signature and the agent token it presents, from agent providers you trust;
- puts each mission an agent proposes to whoever decides for the person, and issues person tokens and auth tokens under it;
- weighs each auth token request against its mission’s budget (draft-hardt-aauth-budgets): one that fits what is left is issued at once and spent, and one that does not goes back to the approver;
- turns whatever needs a human into an
Approval, which you deliver however you reach people, and answer withPersonServer::decide.
It is a port of @introspection-sdk/person-server. The two speak the
same protocol, and an agent cannot tell them apart.
§Running one
You supply a Directory: who you speak for, by the login_hint an
agent names, and who decides for them. Hooks tell you when a decision
is needed. PersonServer::handle takes an http::Request<Bytes> and
returns an http::Response<Bytes>, so it mounts behind hyper, axum, or
anything else built on the http crate; examples/server.rs runs it on
hyper.
use aauth_person_server::{
Approval, ApprovalKind, Config, Decision, Directory, Hooks, Person, PersonServer,
};
use async_trait::async_trait;
use bytes::Bytes;
struct Staff;
#[async_trait]
impl Directory for Staff {
async fn person(&self, login_hint: &str, _tenant: &str) -> Option<Person> {
let email = login_hint.strip_prefix("user:")?.strip_suffix("@acme.example")?;
Some(Person::new(email))
}
async fn approver(&self, kind: ApprovalKind, _person: &Person) -> Option<Person> {
// Managers approve trips; completing one is the traveller's own call.
(kind != ApprovalKind::Completion).then(|| Person::new("dana"))
}
}
struct Notify;
#[async_trait]
impl Hooks for Notify {
async fn on_approval(&self, approval: &Approval) {
println!("ask {} about {}", approval.approver.id, approval.description);
}
}
let ps = PersonServer::new(
Config::new("https://ps.acme.example", Staff, ["https://api.introspection.dev"])
// In production, a stable key and secret; unset, both are fresh per process.
.signing_key(aauth_protocol::SigningKey::generate())
.subject_secret("a long random secret")
.hooks(Notify),
)?;
let request = http::Request::get("/.well-known/aauth-person.json").body(Bytes::new())?;
let response = ps.handle(request).await;
assert_eq!(response.status(), http::StatusCode::OK);
// Later, when Dana answers an approval:
ps.decide("default", approval_id, Decision::approve().by("dana@acme.example")).await;§Policy
- A mission is always put to the approver, who may set its budget.
- An auth token needs a live mission covering the resource token’s issuer, and only those issuers’ keys are ever fetched.
- A request whose
budgetis in the mission’s unit and scale, and fits what is left, is issued at once and spent. Any other budgeted request is put to the approver, and spent when approved. - A request with no
budgetis issued within the mission without spending. - Completing a mission is the person’s own decision; nothing is issued under it afterwards.
- Each agent key is bound to the first person it names.
§Routes
| Route | |
|---|---|
GET /.well-known/aauth-person.json | metadata |
GET /.well-known/jwks.json | token-signing keys |
POST /mission | propose a mission |
POST /mission/{s256} | update or completion |
POST /person-token | a person token |
POST /token | an auth token, for a resource token |
GET, DELETE /pending/{tenant}/{id} | poll, or withdraw, a pending request |
With Config::admin_token, the management routes are on too, behind
Authorization: Bearer <token>: GET /consent[?status=],
POST /consent/{id}/decision, GET /missions[/{s256}], and
PATCH /missions/{s256} to terminate one; each takes ?tenant=.
§Tenants
A Person Server can serve many organisations at once. An agent names its
AAuth tenant in each request body, and a request that names none is
under the server’s default tenant (Config::default_tenant,
DEFAULT_TENANT unless set; Config::require_tenant refuses such a
request instead). The pending URL carries the tenant, tokens carry the
one the agent named or the directory gave, and every Store call is
scoped by it, so one tenant’s missions, approvals and key bindings are
never another’s. A person the directory places in a tenant is in that
tenant alone.
§Deploying
MemoryStoreis one process’s memory, and keeps missions and key bindings forever. For more than one process, implementStoreon a database: itsspend,transition_approvalandinsert_missionmust be atomic, which is what makes each budget spend and each decision happen once, and keeps one mission from replacing another. A store that fails answers with aStoreError, which the agent sees as a503.- Bound request bodies before handing them over (the example caps them at 256 KiB), and rate-limit in front: this crate does neither.
- Only a trusted agent provider’s keys, a mission’s resources’ keys, and
a resource’s own R3 documents are ever fetched, through
ReqwestFetch::guarded(thereqwestfeature, on by default):httpsonly, never an IP literal,localhostor a private address. GiveConfig::fetchyour own to change that. - An approved auth token is collected once: the poll that receives it
moves the approval to
ApprovalStatus::Collected, and later polls are410 Gone.
Re-exports§
pub use aauth_protocol;
Structs§
- AAuth
Error - An AAuth error, answered as
application/problem+json. - Agent
- The agent a request came from, once its signature and agent token verify.
- Approval
- A decision this Person Server waits on. Whoever operates it makes the decision.
- Config
- How a Person Server is set up.
issuer, the directory and the trusted agent providers are required; everything else has a default. - Decision
- What an operator answers.
- Endpoints
- The URLs a Person Server answers at.
- Issued
- A token this Person Server issued, as
crate::Hooks::on_issuedreports it. - LogEntry
- An update the agent logged against a mission.
- Memory
Store - Everything in memory: for development, tests and single-process demos. Expired approvals are dropped a day after they expire; nothing else is ever dropped, and nothing survives a restart.
- Mission
- An approved mission.
- NoHooks
- No hooks.
- Person
- Someone this Person Server speaks for.
- Person
Server - A Person Server. Build one with
PersonServer::newfrom aConfig, then hand it requests withPersonServer::handle. - Store
Error - A store that could not answer, such as a database that is down. The
Person Server answers the request with a
503.
Enums§
- Approval
Kind - What an approval decides.
- Approval
Status - Where an approval stands.
- Issued
Kind - Which token was issued.
- Spend
- Whether
Store::spendmay take a mission past its budget. - Verdict
- An approver’s answer.
Constants§
- DEFAULT_
TENANT - The tenant a request that names none is under, unless the server is set up otherwise.
Traits§
- Directory
- Who this Person Server speaks for, and who decides for them.
- Hooks
- What a Person Server tells its operator.
- Store
- Where a Person Server keeps its state.
Functions§
- problem
- A problem response:
{error, detail?}.
Type Aliases§
- Store
Result - What a
Storeanswers.