Skip to main content

Crate aauth_person_server

Crate aauth_person_server 

Source
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 with PersonServer::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 budget is 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 budget is 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.jsonmetadata
GET /.well-known/jwks.jsontoken-signing keys
POST /missionpropose a mission
POST /mission/{s256}update or completion
POST /person-tokena person token
POST /tokenan 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

  • MemoryStore is one process’s memory, and keeps missions and key bindings forever. For more than one process, implement Store on a database: its spend, transition_approval and insert_mission must 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 a StoreError, which the agent sees as a 503.
  • 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 (the reqwest feature, on by default): https only, never an IP literal, localhost or a private address. Give Config::fetch your 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 are 410 Gone.

Re-exports§

pub use aauth_protocol;

Structs§

AAuthError
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_issued reports it.
LogEntry
An update the agent logged against a mission.
MemoryStore
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.
PersonServer
A Person Server. Build one with PersonServer::new from a Config, then hand it requests with PersonServer::handle.
StoreError
A store that could not answer, such as a database that is down. The Person Server answers the request with a 503.

Enums§

ApprovalKind
What an approval decides.
ApprovalStatus
Where an approval stands.
IssuedKind
Which token was issued.
Spend
Whether Store::spend may 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§

StoreResult
What a Store answers.