Skip to main content

aauth_person_server/
server.rs

1//! The Person Server: verifies each agent by its HTTP signature and agent
2//! token, approves missions, issues person and auth tokens, and weighs each
3//! auth token request against its mission's budget. Whatever needs a human
4//! becomes an [`Approval`], decided through [`PersonServer::decide`] or the
5//! management routes.
6
7use std::sync::Arc;
8use std::time::Duration;
9
10use aauth_protocol::discovery::{Fetch, JwksResolver, KeyResolver, NoKeys};
11use aauth_protocol::httpsig::{self, Incoming, SignatureKey, VerifyOptions};
12use aauth_protocol::identifier::{is_server_identifier, trim};
13use aauth_protocol::jwk::{Algorithm, Jwks};
14use aauth_protocol::metadata::{dwk, PersonServerMetadata};
15use aauth_protocol::token::{mint, verify_token, TokenKind, VerifyTokenOptions};
16use aauth_protocol::{jwt, Budget, SigningKey};
17use base64::engine::general_purpose::URL_SAFE_NO_PAD;
18use base64::Engine;
19use bytes::Bytes;
20use hmac::{Hmac, Mac};
21use http::{HeaderValue, Method, Request, Response, StatusCode};
22use percent_encoding::{percent_decode_str, utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
23use serde::Serialize;
24use serde_json::{json, Map, Value};
25use sha2::Sha256;
26
27use crate::directory::{Directory, Hooks, NoHooks};
28use crate::error::{problem, AAuthError};
29use crate::store::{MemoryStore, Spend, Store};
30use crate::time::rfc3339;
31use crate::types::{
32    Agent, Approval, ApprovalKind, ApprovalStatus, Decision, Issued, IssuedKind, LogEntry, Mission,
33    Person, Verdict,
34};
35
36type Clock = Arc<dyn Fn() -> i64 + Send + Sync>;
37type Trust = Arc<dyn Fn(&str) -> bool + Send + Sync>;
38
39/// Which tenant a request that names none is under.
40#[derive(Clone, Debug)]
41enum Tenancy {
42    /// This one.
43    Default(String),
44    /// None: such a request is refused.
45    Required,
46}
47
48/// How a Person Server is set up. `issuer`, the directory and the trusted
49/// agent providers are required; everything else has a default.
50pub struct Config {
51    // Required.
52    issuer: String,
53    directory: Arc<dyn Directory>,
54    trusted: Trust,
55    // Keys: generated per process when unset.
56    signing_key: Option<SigningKey>,
57    subject_secret: Option<String>,
58    // What it runs on.
59    store: Arc<dyn Store>,
60    hooks: Arc<dyn Hooks>,
61    fetch: Option<Arc<dyn Fetch>>,
62    // What it serves.
63    base_path: String,
64    tenancy: Tenancy,
65    admin_token: Option<String>,
66    // Its metadata.
67    name: Option<String>,
68    description: Option<String>,
69    // Timings.
70    token_ttl: Duration,
71    mission_ttl: Duration,
72    approval_ttl: Duration,
73    retry_after: Duration,
74    now: Clock,
75}
76
77/// The tenant a request that names none is under, unless the server is
78/// set up otherwise.
79pub const DEFAULT_TENANT: &str = "default";
80
81impl Config {
82    /// `issuer` is `https://host`, with no port or path. Agents may ask only
83    /// when their agent token's `iss` is one of `trusted_agent_providers`.
84    pub fn new<I, S>(
85        issuer: &str,
86        directory: impl Directory + 'static,
87        trusted_agent_providers: I,
88    ) -> Self
89    where
90        I: IntoIterator<Item = S>,
91        S: AsRef<str>,
92    {
93        let trusted: Vec<String> = trusted_agent_providers
94            .into_iter()
95            .map(|p| trim(p.as_ref()).to_string())
96            .collect();
97        Self {
98            issuer: trim(issuer).to_string(),
99            directory: Arc::new(directory),
100            trusted: Arc::new(move |iss| trusted.iter().any(|t| t == iss)),
101            signing_key: None,
102            subject_secret: None,
103            store: Arc::new(MemoryStore::new()),
104            hooks: Arc::new(NoHooks),
105            fetch: None,
106            base_path: String::new(),
107            tenancy: Tenancy::Default(DEFAULT_TENANT.to_string()),
108            admin_token: None,
109            name: None,
110            description: None,
111            token_ttl: Duration::from_secs(60 * 60),
112            mission_ttl: Duration::from_secs(14 * 24 * 60 * 60),
113            approval_ttl: Duration::from_secs(24 * 60 * 60),
114            retry_after: Duration::from_secs(5),
115            now: Arc::new(aauth_protocol::now_unix),
116        }
117    }
118
119    /// The tenant a request that names none is under. Default:
120    /// [`DEFAULT_TENANT`]. A company's own Person Server names its own.
121    pub fn default_tenant(mut self, tenant: impl Into<String>) -> Self {
122        self.tenancy = Tenancy::Default(tenant.into());
123        self
124    }
125
126    /// Refuse a request that names no tenant: for a Person Server that
127    /// serves several.
128    pub fn require_tenant(mut self) -> Self {
129        self.tenancy = Tenancy::Required;
130        self
131    }
132
133    /// Decide trust in an agent provider by its `iss` instead of a list.
134    pub fn trust(mut self, trusted: impl Fn(&str) -> bool + Send + Sync + 'static) -> Self {
135        self.trusted = Arc::new(trusted);
136        self
137    }
138
139    /// The token-signing key. Unset, a fresh key per process.
140    pub fn signing_key(mut self, key: SigningKey) -> Self {
141        self.signing_key = Some(key);
142        self
143    }
144
145    /// Derives each person's directed `sub` per audience. Unset, a fresh secret per process.
146    pub fn subject_secret(mut self, secret: impl Into<String>) -> Self {
147        self.subject_secret = Some(secret.into());
148        self
149    }
150
151    /// Where state is kept; in memory by default.
152    pub fn store(mut self, store: Arc<dyn Store>) -> Self {
153        self.store = store;
154        self
155    }
156
157    /// Where the routes live under the issuer, e.g. `/ps`. Default: the root.
158    pub fn base_path(mut self, base: &str) -> Self {
159        self.base_path = trim(base).to_string();
160        self
161    }
162
163    /// Tell `hooks` about approvals, decisions and issued tokens.
164    pub fn hooks(mut self, hooks: impl Hooks + 'static) -> Self {
165        self.hooks = Arc::new(hooks);
166        self
167    }
168
169    /// Bearer token for the management routes (`/consent`, `/missions`). Unset
170    /// or empty, they are off.
171    pub fn admin_token(mut self, token: impl Into<String>) -> Self {
172        self.admin_token = Some(token.into()).filter(|t: &String| !t.is_empty());
173        self
174    }
175
176    /// A display name, in the metadata document.
177    pub fn name(mut self, name: impl Into<String>) -> Self {
178        self.name = Some(name.into());
179        self
180    }
181
182    /// A display description, in the metadata document.
183    pub fn description(mut self, description: impl Into<String>) -> Self {
184        self.description = Some(description.into());
185        self
186    }
187
188    /// How long a person or auth token lasts, at most. Default: an hour.
189    pub fn token_ttl(mut self, ttl: Duration) -> Self {
190        self.token_ttl = ttl;
191        self
192    }
193
194    /// How long an approved mission lasts. Default: 14 days.
195    pub fn mission_ttl(mut self, ttl: Duration) -> Self {
196        self.mission_ttl = ttl;
197        self
198    }
199
200    /// How long an approval waits for a decision. Default: 24 hours.
201    pub fn approval_ttl(mut self, ttl: Duration) -> Self {
202        self.approval_ttl = ttl;
203        self
204    }
205
206    /// The `Retry-After` a pending answer carries, in whole seconds. Default:
207    /// 5 seconds.
208    pub fn retry_after(mut self, after: Duration) -> Self {
209        self.retry_after = after;
210        self
211    }
212
213    /// How agent provider, resource and R3 documents are fetched. Unset,
214    /// a guarded reqwest client (with the `reqwest` feature).
215    pub fn fetch(mut self, fetch: Arc<dyn Fetch>) -> Self {
216        self.fetch = Some(fetch);
217        self
218    }
219
220    /// Seconds since the epoch; for tests.
221    pub fn clock(mut self, now: impl Fn() -> i64 + Send + Sync + 'static) -> Self {
222        self.now = Arc::new(now);
223        self
224    }
225}
226
227/// The URLs a Person Server answers at.
228#[derive(Clone, Debug, Serialize)]
229pub struct Endpoints {
230    /// `POST`: a person token.
231    pub person_token: String,
232    /// `POST`: an auth token, for a resource token.
233    pub token: String,
234    /// `POST`: propose a mission; `POST {mission}/{s256}` acts on one.
235    pub mission: String,
236    pending_prefix: String,
237}
238
239impl Endpoints {
240    /// Where the agent polls approval `id`, asked under `tenant`.
241    pub fn pending(&self, tenant: &str, id: &str) -> String {
242        format!(
243            "{}{}/{id}",
244            self.pending_prefix,
245            utf8_percent_encode(tenant, SEGMENT)
246        )
247    }
248}
249
250/// A Person Server. Build one with [`PersonServer::new`] from a [`Config`],
251/// then hand it requests with [`PersonServer::handle`].
252pub struct PersonServer {
253    config: Config,
254    // Resolved from the config: what it leaves unset is made here.
255    key: SigningKey,
256    secret: String,
257    fetch: Arc<dyn Fetch>,
258    // Derived from the config.
259    authority: String,
260    resolver: JwksResolver,
261    endpoints: Endpoints,
262}
263
264/// A duration in whole seconds, as token and mission times are kept.
265fn secs(duration: Duration) -> i64 {
266    i64::try_from(duration.as_secs()).unwrap_or(i64::MAX)
267}
268
269fn random_hex(bytes: usize) -> String {
270    let mut buf = vec![0u8; bytes];
271    getrandom::fill(&mut buf).expect("the operating system provides randomness");
272    buf.iter().map(|b| format!("{b:02x}")).collect()
273}
274
275fn read_body(body: &[u8]) -> Result<Map<String, Value>, AAuthError> {
276    match serde_json::from_slice(body) {
277        Ok(Value::Object(map)) => Ok(map),
278        _ => Err(AAuthError::invalid_request("body must be a JSON object")),
279    }
280}
281
282fn str_of<'a>(params: &'a Map<String, Value>, name: &str) -> Option<&'a str> {
283    params.get(name).and_then(Value::as_str)
284}
285
286fn json_response(status: StatusCode, body: &impl Serialize) -> Response<Bytes> {
287    let mut response = Response::new(Bytes::from(serde_json::to_vec(body).unwrap_or_default()));
288    *response.status_mut() = status;
289    response
290        .headers_mut()
291        .insert("content-type", HeaderValue::from_static("application/json"));
292    response
293}
294
295fn ok(body: &impl Serialize) -> Response<Bytes> {
296    json_response(StatusCode::OK, body)
297}
298
299/// What a tenant escapes to in a pending URL's path segment.
300const SEGMENT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'_').remove(b'.');
301
302/// The tenant a request body names, if any.
303fn named(params: &Map<String, Value>) -> Option<&str> {
304    str_of(params, "tenant").filter(|t| !t.is_empty())
305}
306
307/// A pending approval's grant claims, kept to the scalar claims a resource token carries.
308const GRANT_CLAIMS: &[&str] = &["scope", "r3_uri", "r3_s256"];
309
310impl PersonServer {
311    /// A Person Server as `config` describes it. Fails when the issuer is
312    /// not a server identifier, or no [`Fetch`] is given without the
313    /// `reqwest` feature.
314    pub fn new(mut config: Config) -> aauth_protocol::Result<Self> {
315        if !is_server_identifier(&config.issuer) {
316            return Err(aauth_protocol::Error::Invalid(format!(
317                "issuer {} must be https://host with no port or path",
318                config.issuer
319            )));
320        }
321        let fetch = match config.fetch.take() {
322            Some(fetch) => fetch,
323            None => default_fetch()?,
324        };
325        let authority = url::Url::parse(&config.issuer)
326            .ok()
327            .and_then(|u| u.host_str().map(str::to_string))
328            .unwrap_or_default();
329        let url = |path: &str| format!("{}{}{path}", config.issuer, config.base_path);
330        let endpoints = Endpoints {
331            person_token: url("/person-token"),
332            token: url("/token"),
333            mission: url("/mission"),
334            pending_prefix: url("/pending/"),
335        };
336        Ok(Self {
337            key: config
338                .signing_key
339                .take()
340                .unwrap_or_else(SigningKey::generate),
341            secret: config
342                .subject_secret
343                .take()
344                .unwrap_or_else(|| random_hex(32)),
345            resolver: JwksResolver::new(fetch.clone()),
346            fetch,
347            authority,
348            endpoints,
349            config,
350        })
351    }
352
353    /// Its server identifier.
354    pub fn issuer(&self) -> &str {
355        &self.config.issuer
356    }
357
358    /// The URLs it answers at.
359    pub fn endpoints(&self) -> &Endpoints {
360        &self.endpoints
361    }
362
363    /// Where its state is kept.
364    pub fn store(&self) -> &Arc<dyn Store> {
365        &self.config.store
366    }
367
368    /// Its `aauth-person.json`.
369    pub fn metadata(&self) -> PersonServerMetadata {
370        PersonServerMetadata {
371            issuer: self.config.issuer.clone(),
372            auth_token_endpoint: self.endpoints.token.clone(),
373            person_token_endpoint: Some(self.endpoints.person_token.clone()),
374            mission_endpoint: Some(self.endpoints.mission.clone()),
375            jwks_uri: Some(format!("{}/.well-known/jwks.json", self.config.issuer)),
376            name: self.config.name.clone(),
377            description: self.config.description.clone(),
378        }
379    }
380
381    /// The keys its tokens are signed with, as `/.well-known/jwks.json` serves them.
382    pub fn jwks(&self) -> Jwks {
383        self.key.jwks()
384    }
385
386    fn now(&self) -> i64 {
387        (self.config.now)()
388    }
389
390    fn default_tenant(&self) -> Option<&str> {
391        match &self.config.tenancy {
392            Tenancy::Default(tenant) => Some(tenant),
393            Tenancy::Required => None,
394        }
395    }
396
397    /// The tenant a request is under: the one it names, else the default.
398    fn tenant(&self, named: Option<&str>) -> Result<String, AAuthError> {
399        named
400            .map(str::to_string)
401            .or_else(|| self.default_tenant().map(str::to_string))
402            .ok_or_else(|| AAuthError::invalid_request("name the tenant"))
403    }
404
405    /// Answer any request; AAuth errors become problem responses and a
406    /// request this server does not serve a `404`.
407    pub async fn handle(&self, request: Request<Bytes>) -> Response<Bytes> {
408        let path = request.uri().path().to_string();
409        self.handle_at(request, &path).await
410    }
411
412    /// [`Self::handle`], for the `pathname` the agent addressed when a
413    /// framework rewrote the request's URI.
414    pub async fn handle_at(&self, request: Request<Bytes>, pathname: &str) -> Response<Bytes> {
415        match self.route(&request, pathname).await {
416            Ok(Some(response)) => response,
417            Ok(None) => problem(StatusCode::NOT_FOUND, "not_found", None),
418            Err(error) => error.into_response(),
419        }
420    }
421
422    /// The response to a request this server serves; `None` for any other.
423    pub async fn route(
424        &self,
425        request: &Request<Bytes>,
426        pathname: &str,
427    ) -> Result<Option<Response<Bytes>>, AAuthError> {
428        let method = request.method();
429        if method == Method::GET && pathname == format!("/.well-known/{}", dwk::PERSON) {
430            return Ok(Some(ok(&self.metadata())));
431        }
432        if method == Method::GET && pathname == "/.well-known/jwks.json" {
433            return Ok(Some(ok(&self.jwks())));
434        }
435        let Some(path) = pathname.strip_prefix(self.config.base_path.as_str()) else {
436            return Ok(None);
437        };
438        if !self.config.base_path.is_empty() && !path.starts_with('/') {
439            return Ok(None);
440        }
441        let segments: Vec<&str> = path.trim_start_matches('/').split('/').collect();
442        let response = match (method, segments.as_slice()) {
443            (&Method::POST, ["mission"]) => self.propose_mission(request, path).await?,
444            (&Method::POST, ["mission", s256]) if !s256.is_empty() => {
445                self.mission_action(request, path, s256).await?
446            }
447            (&Method::POST, ["person-token"]) => self.request_person_token(request, path).await?,
448            (&Method::POST, ["token"]) => self.request_auth_token(request, path).await?,
449            (&Method::GET | &Method::DELETE, ["pending", id]) if !id.is_empty() => {
450                let tenant = self.tenant(None)?;
451                self.pending_state(request, path, &tenant, id).await?
452            }
453            (&Method::GET | &Method::DELETE, ["pending", tenant, id])
454                if !tenant.is_empty() && !id.is_empty() =>
455            {
456                let tenant = percent_decode_str(tenant).decode_utf8_lossy();
457                self.pending_state(request, path, &tenant, id).await?
458            }
459            _ => return self.manage(request, &segments).await,
460        };
461        Ok(Some(response))
462    }
463
464    /// The request's signature, and the agent token it presents, from a
465    /// trusted agent provider.
466    async fn verify_agent(
467        &self,
468        request: &Request<Bytes>,
469        path: &str,
470    ) -> Result<Agent, AAuthError> {
471        let full_path = format!("{}{path}", self.config.base_path);
472        let body = request.body();
473        let verified = httpsig::verify(
474            &Incoming {
475                method: request.method().as_str(),
476                // As the agent addressed it: this identifier and the route's
477                // path, whatever routed the request here.
478                authority: &self.authority,
479                path: &full_path,
480                query: request.uri().query(),
481                headers: request.headers(),
482                body: (!body.is_empty()).then_some(body.as_ref()),
483            },
484            &VerifyOptions {
485                now: self.now(),
486                require_content_digest: true,
487                algorithms: vec![Algorithm::Ed25519, Algorithm::Es256],
488                ..VerifyOptions::default()
489            },
490            // The scheme must be `jwt`: nothing is fetched on an
491            // unauthenticated caller's say-so.
492            &NoKeys,
493        )
494        .await
495        .map_err(|e| AAuthError::signature(e.code, e.detail))?;
496        let Some(raw) = verified.jwt() else {
497            return Err(AAuthError::signature(
498                "unsupported_scheme",
499                "sign with sig=jwt",
500            ));
501        };
502        let decoded =
503            jwt::decode(raw).map_err(|_| AAuthError::signature("invalid_jwt", "not a JWT"))?;
504        let provider = trim(decoded.claim_str("iss").unwrap_or_default()).to_string();
505        if !(self.config.trusted)(&provider) {
506            return Err(AAuthError::signature(
507                "invalid_jwt",
508                format!("{provider} is not trusted here"),
509            ));
510        }
511        let token = verify_token(
512            &VerifyTokenOptions {
513                jwt: raw,
514                http_signature_thumbprint: &verified.thumbprint,
515                resource: &self.config.issuer,
516                accept: &[TokenKind::Agent],
517                now: self.now(),
518                clock_tolerance: 60,
519            },
520            &self.resolver,
521        )
522        .await
523        .map_err(|e| {
524            let code = if e.code == "token_expired" {
525                "expired_jwt"
526            } else {
527                "invalid_jwt"
528            };
529            AAuthError::signature(code, e.detail)
530        })?;
531        if token.ps().is_some_and(|ps| trim(ps) != self.config.issuer) {
532            return Err(AAuthError::forbidden(
533                "this agent's Person Server is another",
534            ));
535        }
536        Ok(Agent {
537            id: token.sub,
538            provider,
539            jkt: verified.thumbprint,
540            jwk: token.cnf_jwk,
541            exp: token.exp,
542        })
543    }
544
545    /// The person the agent acts for, under `tenant`. Each agent key is
546    /// bound to the first person it names. Tokens carry the directory's
547    /// tenant, else the one the agent `named`.
548    async fn person_for(
549        &self,
550        tenant: &str,
551        named: Option<&str>,
552        agent: &Agent,
553        login_hint: Option<&str>,
554    ) -> Result<Person, AAuthError> {
555        let Some(hint) = login_hint else {
556            return self
557                .config
558                .store
559                .binding(tenant, &agent.jkt)
560                .await?
561                .ok_or_else(|| AAuthError::invalid_request("name the person: login_hint"));
562        };
563        let Some(hinted) = self.config.directory.person(hint, tenant).await else {
564            return Err(AAuthError::invalid_request(
565                "not someone this Person Server speaks for",
566            ));
567        };
568        // The directory's tenant is the person's; a request can only be
569        // under it, never change it.
570        if hinted
571            .tenant
572            .as_deref()
573            .is_some_and(|theirs| theirs != tenant)
574        {
575            return Err(AAuthError::forbidden("this person is not in that tenant"));
576        }
577        let hinted = Person {
578            tenant: hinted.tenant.or_else(|| named.map(str::to_string)),
579            ..hinted
580        };
581        let bound = self.config.store.bind(tenant, &agent.jkt, &hinted).await?;
582        if bound.id != hinted.id {
583            return Err(AAuthError::forbidden("this agent acts for another person"));
584        }
585        Ok(hinted)
586    }
587
588    /// The person's identifier at one audience: no two audiences can correlate it.
589    fn directed(&self, person: &Person, audience: &str) -> String {
590        let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(self.secret.as_bytes())
591            .expect("HMAC takes any key");
592        mac.update(format!("{}\n{audience}", person.id).as_bytes());
593        URL_SAFE_NO_PAD.encode(mac.finalize().into_bytes())
594    }
595
596    fn issue(
597        &self,
598        kind: TokenKind,
599        claims: Map<String, Value>,
600        not_after: i64,
601        person: &Person,
602    ) -> (String, i64) {
603        let iat = self.now();
604        let exp = (iat + secs(self.config.token_ttl)).min(not_after);
605        let mut all = Map::new();
606        all.insert("iss".into(), json!(self.config.issuer));
607        all.insert("dwk".into(), json!(dwk::PERSON));
608        all.insert("jti".into(), json!(uuid::Uuid::new_v4().to_string()));
609        all.insert("iat".into(), json!(iat));
610        all.insert("exp".into(), json!(exp));
611        if let Some(tenant) = &person.tenant {
612            all.insert("tenant".into(), json!(tenant));
613        }
614        all.extend(claims);
615        (mint(&self.key, kind, &Value::Object(all)), exp - iat)
616    }
617
618    async fn person_token(
619        &self,
620        agent: &Agent,
621        person: &Person,
622        resource: &str,
623        mission: Option<&Mission>,
624    ) -> Value {
625        let mut claims = Map::new();
626        claims.insert("aud".into(), json!(resource));
627        claims.insert("sub".into(), json!(self.directed(person, resource)));
628        claims.insert("cnf".into(), json!({ "jwk": agent.jwk }));
629        if let Some(mission) = mission {
630            claims.insert("mission_s256".into(), json!(mission.s256));
631        }
632        let not_after = agent.exp.min(mission.map_or(i64::MAX, |m| m.expires_at));
633        let (token, expires_in) = self.issue(TokenKind::Person, claims, not_after, person);
634        self.config
635            .hooks
636            .on_issued(&Issued {
637                kind: IssuedKind::PersonToken,
638                agent: agent.clone(),
639                person: person.clone(),
640                resource: resource.to_string(),
641                mission: mission.cloned(),
642                budget: None,
643            })
644            .await;
645        json!({ "person_token": token, "expires_in": expires_in })
646    }
647
648    async fn auth_token(
649        &self,
650        agent: &Agent,
651        mission: &Mission,
652        grant: &Map<String, Value>,
653    ) -> Value {
654        let mut claims = Map::new();
655        claims.insert("ps".into(), json!(self.config.issuer));
656        claims.insert("cnf".into(), json!({ "jwk": agent.jwk }));
657        claims.insert("mission_s256".into(), json!(mission.s256));
658        claims.extend(grant.clone());
659        let (token, expires_in) = self.issue(
660            TokenKind::Auth,
661            claims,
662            agent.exp.min(mission.expires_at),
663            &mission.person,
664        );
665        self.config
666            .hooks
667            .on_issued(&Issued {
668                kind: IssuedKind::AuthToken,
669                agent: agent.clone(),
670                person: mission.person.clone(),
671                resource: grant
672                    .get("aud")
673                    .and_then(Value::as_str)
674                    .unwrap_or_default()
675                    .to_string(),
676                mission: Some(mission.clone()),
677                budget: grant
678                    .get("budget")
679                    .and_then(|b| serde_json::from_value(b.clone()).ok()),
680            })
681            .await;
682        json!({ "auth_token": token, "expires_in": expires_in })
683    }
684
685    async fn request_person_token(
686        &self,
687        request: &Request<Bytes>,
688        path: &str,
689    ) -> Result<Response<Bytes>, AAuthError> {
690        let agent = self.verify_agent(request, path).await?;
691        let params = read_body(request.body())?;
692        let resource = trim(str_of(&params, "resource").unwrap_or_default()).to_string();
693        if !resource.starts_with("https://") {
694            return Err(AAuthError::invalid_request("resource is an https origin"));
695        }
696        let tenant = self.tenant(named(&params))?;
697        let person = self
698            .person_for(
699                &tenant,
700                named(&params),
701                &agent,
702                str_of(&params, "login_hint"),
703            )
704            .await?;
705        let mission = match params.get("mission_s256") {
706            None | Some(Value::Null) => None,
707            Some(hash) => Some(self.live_mission(&tenant, &agent, hash.as_str()).await?),
708        };
709        if mission.as_ref().is_some_and(|m| m.person.id != person.id) {
710            return Err(AAuthError::mission_not_found());
711        }
712        Ok(ok(&self
713            .person_token(&agent, &person, &resource, mission.as_ref())
714            .await))
715    }
716
717    /// The live mission `s256` names, if it is this agent's; the same 404 otherwise.
718    async fn live_mission(
719        &self,
720        tenant: &str,
721        agent: &Agent,
722        s256: Option<&str>,
723    ) -> Result<Mission, AAuthError> {
724        let mission = match s256 {
725            Some(s256) => self.config.store.mission(tenant, s256).await?,
726            None => None,
727        };
728        let Some(mission) = mission.filter(|m| m.agent_id == agent.id) else {
729            return Err(AAuthError::mission_not_found());
730        };
731        // The agent identifier is the provider's; the person is the key's.
732        // Another key under the same identifier, bound to someone else, gets
733        // the same 404 as a stranger.
734        if self
735            .config
736            .store
737            .binding(tenant, &agent.jkt)
738            .await?
739            .map(|p| p.id)
740            != Some(mission.person.id.clone())
741        {
742            return Err(AAuthError::mission_not_found());
743        }
744        if mission.completed_at.is_some() {
745            return Err(AAuthError::mission_terminated("mission completed"));
746        }
747        if self.now() >= mission.expires_at {
748            return Err(AAuthError::mission_terminated("mission expired"));
749        }
750        Ok(mission)
751    }
752
753    async fn propose_mission(
754        &self,
755        request: &Request<Bytes>,
756        path: &str,
757    ) -> Result<Response<Bytes>, AAuthError> {
758        let agent = self.verify_agent(request, path).await?;
759        let params = read_body(request.body())?;
760        let tenant = self.tenant(named(&params))?;
761        let person = self
762            .person_for(
763                &tenant,
764                named(&params),
765                &agent,
766                str_of(&params, "login_hint"),
767            )
768            .await?;
769        let description = str_of(&params, "description").unwrap_or_default();
770        if description.is_empty() {
771            return Err(AAuthError::invalid_request("describe the mission"));
772        }
773        let resources: Vec<Value> = params
774            .get("resources")
775            .and_then(Value::as_array)
776            .into_iter()
777            .flatten()
778            .filter_map(Value::as_str)
779            .map(|r| trim(r).to_string())
780            .filter(|r| r.starts_with("https://"))
781            .map(Value::from)
782            .collect();
783        let mut grant = Map::new();
784        grant.insert("resources".into(), Value::Array(resources));
785        let approval = self.approval(
786            &tenant,
787            ApprovalKind::Mission,
788            agent,
789            person,
790            description.to_string(),
791        );
792        self.ask(Approval { grant, ..approval }).await
793    }
794
795    async fn mission_action(
796        &self,
797        request: &Request<Bytes>,
798        path: &str,
799        s256: &str,
800    ) -> Result<Response<Bytes>, AAuthError> {
801        let agent = self.verify_agent(request, path).await?;
802        let params = read_body(request.body())?;
803        let tenant = self.tenant(named(&params))?;
804        let tenant = tenant.as_str();
805        let mission = self.live_mission(tenant, &agent, Some(s256)).await?;
806        match str_of(&params, "action") {
807            Some("update") => {
808                let entry = str_of(&params, "description")
809                    .unwrap_or_default()
810                    .to_string();
811                let hash = aauth_protocol::s256(&entry);
812                let now = self.now();
813                if !self
814                    .config
815                    .store
816                    .log_mission(tenant, &mission.s256, LogEntry { at: now, entry }, now)
817                    .await?
818                {
819                    return Err(AAuthError::mission_terminated("mission ended"));
820                }
821                Ok(ok(&json!({ "s256": hash })))
822            }
823            Some("completion") => {
824                let summary =
825                    str_of(&params, "summary").unwrap_or("The agent says the mission is done");
826                let approval = self.approval(
827                    tenant,
828                    ApprovalKind::Completion,
829                    agent,
830                    mission.person.clone(),
831                    summary.to_string(),
832                );
833                self.ask(Approval {
834                    mission_s256: Some(mission.s256),
835                    ..approval
836                })
837                .await
838            }
839            _ => Err(AAuthError::invalid_request(
840                "action is update or completion",
841            )),
842        }
843    }
844
845    /// A resource token, verified against its issuer's keys, binding a token
846    /// this Person Server issued.
847    async fn verify_resource_token(
848        &self,
849        agent: &Agent,
850        params: &Map<String, Value>,
851    ) -> Result<(Map<String, Value>, Mission, String), AAuthError> {
852        let (Some(resource_token), Some(presented_token)) = (
853            str_of(params, "resource_token"),
854            str_of(params, "presented_token"),
855        ) else {
856            return Err(AAuthError::invalid_request(
857                "resource_token and presented_token are required",
858            ));
859        };
860        let invalid = |detail: &str| {
861            AAuthError::new(StatusCode::BAD_REQUEST, "invalid_resource_token", detail)
862        };
863
864        let presented = jwt::decode(presented_token)
865            .ok()
866            .filter(|p| p.kid() == Some(self.key.thumbprint()) || p.kid().is_none())
867            .filter(|p| p.verify_signature(&self.key.public_jwk(), Algorithm::Ed25519))
868            .filter(|p| p.claim_str("iss") == Some(self.config.issuer.as_str()))
869            .filter(|p| {
870                [TokenKind::Person.typ(), TokenKind::Auth.typ()]
871                    .contains(&p.typ().unwrap_or_default())
872            })
873            // Issued to this agent's key, not another's.
874            .filter(|p| {
875                p.claims
876                    .get("cnf")
877                    .and_then(|c| c.get("jwk"))
878                    .and_then(|j| serde_json::from_value::<aauth_protocol::Jwk>(j.clone()).ok())
879                    .and_then(|j| j.thumbprint().ok())
880                    .as_deref()
881                    == Some(agent.jkt.as_str())
882            })
883            .ok_or_else(|| {
884                AAuthError::new(
885                    StatusCode::BAD_REQUEST,
886                    "invalid_presented_token",
887                    "not a live token this Person Server issued",
888                )
889            })?;
890        if presented
891            .claims
892            .get("exp")
893            .and_then(Value::as_i64)
894            .is_none_or(|exp| self.now() >= exp)
895        {
896            return Err(AAuthError::new(
897                StatusCode::BAD_REQUEST,
898                "expired_presented_token",
899                "not a live token this Person Server issued",
900            ));
901        }
902
903        let unverified = jwt::decode(resource_token).map_err(|_| invalid("not a JWT"))?;
904        if unverified.typ() != Some(TokenKind::Resource.typ()) {
905            return Err(invalid("not a resource token"));
906        }
907        let resource = trim(unverified.claim_str("iss").unwrap_or_default()).to_string();
908        // The mission's approved resources are the only issuers whose keys are fetched.
909        let tenant = self.tenant(named(params))?;
910        let tenant = tenant.as_str();
911        // The request is under the tenant the presented token was issued
912        // under; a token that names none was issued under the default.
913        let issued_under = presented
914            .claim_str("tenant")
915            .map(str::to_string)
916            .or_else(|| self.default_tenant().map(str::to_string));
917        if issued_under.as_deref() != Some(tenant) {
918            return Err(invalid("tenant does not match the presented token"));
919        }
920        let mission = match unverified.claim_str("mission_s256") {
921            Some(s256) => self.live_mission(tenant, agent, Some(s256)).await?,
922            None => {
923                return Err(AAuthError::new(
924                    StatusCode::FORBIDDEN,
925                    "access_denied",
926                    "an auth token is issued only within a mission",
927                ))
928            }
929        };
930        if !mission.resources.contains(&resource) {
931            return Err(AAuthError::new(
932                StatusCode::FORBIDDEN,
933                "access_denied",
934                "this mission does not cover that resource",
935            ));
936        }
937        let jwks = self
938            .resolver
939            .jwks(&resource, dwk::RESOURCE)
940            .await
941            .map_err(|e| invalid(&e.to_string()))?;
942        let alg = unverified
943            .alg()
944            .and_then(Algorithm::from_name)
945            .ok_or_else(|| invalid("unsupported alg"))?;
946        let key = match unverified.kid() {
947            Some(kid) => jwks.keys.iter().find(|k| k.kid.as_deref() == Some(kid)),
948            None if jwks.keys.len() == 1 => jwks.keys.first(),
949            None => None,
950        }
951        .ok_or_else(|| invalid("no matching key in the resource's JWKS"))?;
952        if key.alg.as_deref().is_some_and(|a| a != alg.as_str())
953            || !unverified.verify_signature(key, alg)
954        {
955            return Err(invalid("signature verification failed"));
956        }
957        let rt = unverified.claims;
958        if rt
959            .get("exp")
960            .and_then(Value::as_i64)
961            .is_none_or(|exp| self.now() >= exp)
962        {
963            return Err(invalid("resource token has expired"));
964        }
965        let not_before = rt.get("nbf").and_then(Value::as_i64);
966        let issued_at = rt.get("iat").and_then(Value::as_i64);
967        if not_before.is_some_and(|nbf| self.now() < nbf)
968            || issued_at.is_some_and(|iat| iat > self.now() + 60)
969        {
970            return Err(invalid("resource token is not yet valid"));
971        }
972        if str_of(&rt, "aud") != Some(self.config.issuer.as_str())
973            || trim(str_of(&rt, "ps").unwrap_or_default()) != self.config.issuer
974        {
975            return Err(invalid("not for this Person Server"));
976        }
977        if str_of(&rt, "agent_jkt") != Some(agent.jkt.as_str()) {
978            return Err(invalid("agent_jkt is not the signer"));
979        }
980        let p = &presented.claims;
981        if p.get("jti") != rt.get("presented_jti")
982            || p.get("sub") != rt.get("sub")
983            || p.get("mission_s256") != rt.get("mission_s256")
984            || p.get("tenant") != rt.get("tenant")
985            || str_of(p, "aud") != Some(resource.as_str())
986        {
987            return Err(invalid("does not match the presented token"));
988        }
989        Ok((rt, mission, resource))
990    }
991
992    /// The resource's one-line description of the request, from its R3
993    /// proposal; `None` if it has none or it cannot be read.
994    async fn proposal_summary(&self, rt: &Map<String, Value>) -> Option<String> {
995        let (uri, expected) = (str_of(rt, "r3_uri")?, str_of(rt, "r3_s256")?);
996        // Fetched signed as this server, so only from the resource itself.
997        let resource = str_of(rt, "iss")?;
998        if !uri.starts_with("https://") || !aauth_protocol::identifier::same_origin(resource, uri) {
999            return None;
1000        }
1001        let signed = httpsig::sign(
1002            &self.key,
1003            &SignatureKey::JwksUri {
1004                id: self.config.issuer.clone(),
1005                dwk: dwk::PERSON.into(),
1006                kid: self.key.thumbprint().into(),
1007            },
1008            &httpsig::Outgoing {
1009                method: "GET",
1010                url: uri,
1011                content_type: None,
1012                body: None,
1013                created: self.now(),
1014            },
1015        )
1016        .ok()?;
1017        let mut headers = http::HeaderMap::new();
1018        for (name, value) in signed {
1019            headers.insert(
1020                http::HeaderName::from_bytes(name.as_bytes()).ok()?,
1021                HeaderValue::from_str(&value).ok()?,
1022            );
1023        }
1024        let fetched = self.fetch.get(uri, headers).await.ok()?;
1025        if !fetched.status.is_success() || aauth_protocol::s256(&fetched.body) != expected {
1026            return None;
1027        }
1028        let document: Value = serde_json::from_slice(&fetched.body).ok()?;
1029        document
1030            .get("display")?
1031            .get("summary")?
1032            .as_str()
1033            .map(str::to_string)
1034    }
1035
1036    async fn request_auth_token(
1037        &self,
1038        request: &Request<Bytes>,
1039        path: &str,
1040    ) -> Result<Response<Bytes>, AAuthError> {
1041        let agent = self.verify_agent(request, path).await?;
1042        let params = read_body(request.body())?;
1043        let (rt, mission, resource) = self.verify_resource_token(&agent, &params).await?;
1044        let tenant = mission.tenant.clone();
1045        let tenant = tenant.as_str();
1046        let budget = Budget::from_value(rt.get("budget")).map_err(|_| {
1047            AAuthError::new(
1048                StatusCode::BAD_REQUEST,
1049                "invalid_resource_token",
1050                "budget is not a budget object",
1051            )
1052        })?;
1053        let mut grant = Map::new();
1054        grant.insert("aud".into(), json!(resource));
1055        grant.insert("sub".into(), rt.get("sub").cloned().unwrap_or(Value::Null));
1056        for claim in GRANT_CLAIMS {
1057            if let Some(value) = rt.get(*claim) {
1058                grant.insert((*claim).into(), value.clone());
1059            }
1060        }
1061        let Some(budget) = budget else {
1062            return Ok(ok(&self.auth_token(&agent, &mission, &grant).await));
1063        };
1064        grant.insert("budget".into(), json!(budget));
1065        if mission
1066            .budget
1067            .as_ref()
1068            .is_some_and(|b| b.same_scale(&budget))
1069        {
1070            if let Some(spent) = self
1071                .config
1072                .store
1073                .spend(
1074                    tenant,
1075                    &mission.s256,
1076                    budget.amount,
1077                    Spend::WithinBudget,
1078                    self.now(),
1079                )
1080                .await?
1081            {
1082                return Ok(ok(&self.auth_token(&agent, &spent, &grant).await));
1083            }
1084        }
1085        let summary = self.proposal_summary(&rt).await;
1086        let description = str_of(&rt, "scope").unwrap_or("a request").to_string();
1087        let approval = self.approval(
1088            tenant,
1089            ApprovalKind::AuthToken,
1090            agent,
1091            mission.person.clone(),
1092            description,
1093        );
1094        self.ask(Approval {
1095            mission_s256: Some(mission.s256),
1096            resource: Some(resource),
1097            budget: Some(budget),
1098            grant,
1099            summary,
1100            ..approval
1101        })
1102        .await
1103    }
1104
1105    fn approval(
1106        &self,
1107        tenant: &str,
1108        kind: ApprovalKind,
1109        agent: Agent,
1110        person: Person,
1111        description: String,
1112    ) -> Approval {
1113        let created = self.now();
1114        Approval {
1115            id: uuid::Uuid::now_v7().to_string(),
1116            kind,
1117            status: ApprovalStatus::Pending,
1118            agent,
1119            approver: person.clone(),
1120            person,
1121            description,
1122            summary: None,
1123            mission_s256: None,
1124            resource: None,
1125            budget: None,
1126            grant: Map::new(),
1127            decided_budget: None,
1128            decided_by: None,
1129            decided_at: None,
1130            created_at: created,
1131            expires_at: created + secs(self.config.approval_ttl),
1132            tenant: tenant.to_string(),
1133        }
1134    }
1135
1136    async fn ask(&self, mut approval: Approval) -> Result<Response<Bytes>, AAuthError> {
1137        if approval.kind != ApprovalKind::Completion {
1138            if let Some(approver) = self
1139                .config
1140                .directory
1141                .approver(approval.kind, &approval.person)
1142                .await
1143            {
1144                approval.approver = approver;
1145            }
1146        }
1147        let tenant = approval.tenant.as_str();
1148        self.config.store.insert_approval(tenant, &approval).await?;
1149        self.config.hooks.on_approval(&approval).await;
1150        Ok(self.still_pending(tenant, &approval.id))
1151    }
1152
1153    fn still_pending(&self, tenant: &str, id: &str) -> Response<Bytes> {
1154        let mut response = json_response(StatusCode::ACCEPTED, &json!({ "status": "pending" }));
1155        let headers = response.headers_mut();
1156        for (name, value) in [
1157            ("location", self.endpoints.pending(tenant, id)),
1158            ("retry-after", self.config.retry_after.as_secs().to_string()),
1159            ("cache-control", "no-store".to_string()),
1160            (
1161                aauth_protocol::requirement::HEADER,
1162                "requirement=approval".to_string(),
1163            ),
1164        ] {
1165            if let Ok(value) = HeaderValue::from_str(&value) {
1166                headers.insert(name, value);
1167            }
1168        }
1169        response
1170    }
1171
1172    /// The agent polls with a signed GET; only the key that asked may read it.
1173    /// Results are minted now.
1174    async fn pending_state(
1175        &self,
1176        request: &Request<Bytes>,
1177        path: &str,
1178        tenant: &str,
1179        id: &str,
1180    ) -> Result<Response<Bytes>, AAuthError> {
1181        let agent = self.verify_agent(request, path).await?;
1182        let Some(approval) = self
1183            .config
1184            .store
1185            .approval(tenant, id)
1186            .await?
1187            .filter(|a| a.agent.jkt == agent.jkt)
1188        else {
1189            return Ok(problem(
1190                StatusCode::NOT_FOUND,
1191                "not_found",
1192                Some("no such pending request"),
1193            ));
1194        };
1195        let collected = || {
1196            problem(
1197                StatusCode::GONE,
1198                "collected",
1199                Some("this approval's token was already collected"),
1200            )
1201        };
1202        if request.method() == Method::DELETE {
1203            if approval.status == ApprovalStatus::Pending {
1204                let withdrawn = Approval {
1205                    status: ApprovalStatus::Denied,
1206                    decided_by: Some("agent".into()),
1207                    decided_at: Some(self.now()),
1208                    ..approval
1209                };
1210                self.config
1211                    .store
1212                    .transition_approval(tenant, ApprovalStatus::Pending, &withdrawn)
1213                    .await?;
1214            }
1215            let mut response = Response::new(Bytes::new());
1216            *response.status_mut() = StatusCode::NO_CONTENT;
1217            return Ok(response);
1218        }
1219        match approval.status {
1220            ApprovalStatus::Denied => {
1221                let who = approval.approver.name.as_deref().unwrap_or("The approver");
1222                return Ok(problem(
1223                    StatusCode::FORBIDDEN,
1224                    "denied",
1225                    Some(&format!("{who} declined")),
1226                ));
1227            }
1228            ApprovalStatus::Pending if self.now() >= approval.expires_at => {
1229                return Ok(problem(
1230                    StatusCode::REQUEST_TIMEOUT,
1231                    "expired",
1232                    Some("nobody answered in time"),
1233                ));
1234            }
1235            ApprovalStatus::Pending => return Ok(self.still_pending(tenant, id)),
1236            ApprovalStatus::Collected => return Ok(collected()),
1237            ApprovalStatus::Approved => {}
1238        }
1239        if approval.kind == ApprovalKind::Completion {
1240            return Ok(ok(&json!({})));
1241        }
1242        let mission = self
1243            .live_mission(tenant, &agent, approval.mission_s256.as_deref())
1244            .await?;
1245        if approval.kind == ApprovalKind::Mission {
1246            let mut tokens = Map::new();
1247            for resource in &mission.resources {
1248                let issued = self
1249                    .person_token(&agent, &mission.person, resource, Some(&mission))
1250                    .await;
1251                tokens.insert(resource.clone(), issued["person_token"].clone());
1252            }
1253            return Ok(ok(&json!({
1254                "s256": mission.s256,
1255                "mission": URL_SAFE_NO_PAD.encode(mission.mission.as_bytes()),
1256                "person_tokens": tokens,
1257            })));
1258        }
1259        let grant = approval.grant.clone();
1260        let asked = approval.budget.clone();
1261        let collecting = Approval {
1262            status: ApprovalStatus::Collected,
1263            ..approval.clone()
1264        };
1265        // Collecting claims the token, so only one poll spends it.
1266        if !self
1267            .config
1268            .store
1269            .transition_approval(tenant, ApprovalStatus::Approved, &collecting)
1270            .await?
1271        {
1272            return Ok(collected());
1273        }
1274        let mut mission = mission;
1275        // A request in another unit than the mission's budget is approved
1276        // without spending it.
1277        if let Some(asked) =
1278            asked.filter(|a| mission.budget.as_ref().is_some_and(|b| b.same_scale(a)))
1279        {
1280            let spent = self
1281                .config
1282                .store
1283                .spend(
1284                    tenant,
1285                    &mission.s256,
1286                    asked.amount,
1287                    Spend::Approved,
1288                    self.now(),
1289                )
1290                .await;
1291            match spent {
1292                Ok(Some(spent)) => mission = spent,
1293                // Unspent, nothing is issued, and the approval is collectible again.
1294                unspent => {
1295                    let _ = self
1296                        .config
1297                        .store
1298                        .transition_approval(tenant, ApprovalStatus::Collected, &approval)
1299                        .await;
1300                    unspent?;
1301                    return Err(AAuthError::mission_terminated("the mission has ended"));
1302                }
1303            }
1304        }
1305        Ok(ok(&self.auth_token(&agent, &mission, &grant).await))
1306    }
1307
1308    /// Record a decision on approval `id`, asked under `tenant`. Approving
1309    /// does what was asked: creates the mission, or completes it; an
1310    /// approved auth token request is spent when the agent collects its
1311    /// token. A decision is made once: a second, even a
1312    /// concurrent one, is a `409`.
1313    pub async fn decide(
1314        &self,
1315        tenant: &str,
1316        id: &str,
1317        decision: Decision,
1318    ) -> Result<Approval, AAuthError> {
1319        let Some(approval) = self.config.store.approval(tenant, id).await? else {
1320            return Err(AAuthError::new(
1321                StatusCode::NOT_FOUND,
1322                "not_found",
1323                "no such approval",
1324            ));
1325        };
1326        let already =
1327            || AAuthError::new(StatusCode::CONFLICT, "invalid_request", "already decided");
1328        if approval.status != ApprovalStatus::Pending {
1329            return Err(already());
1330        }
1331        let now = self.now();
1332        if now >= approval.expires_at {
1333            return Err(AAuthError::new(
1334                StatusCode::GONE,
1335                "expired",
1336                "this approval has expired",
1337            ));
1338        }
1339        if decision.budget.is_some() && approval.kind != ApprovalKind::Mission {
1340            return Err(AAuthError::invalid_request("only a mission takes a budget"));
1341        }
1342        let approved = decision.status == Verdict::Approved;
1343        let mut decided = Approval {
1344            status: if approved {
1345                ApprovalStatus::Approved
1346            } else {
1347                ApprovalStatus::Denied
1348            },
1349            decided_budget: decision.budget.clone(),
1350            decided_by: decision.decided_by,
1351            decided_at: Some(now),
1352            ..approval.clone()
1353        };
1354        let created = (approved && approval.kind == ApprovalKind::Mission)
1355            .then(|| self.mission_from(&approval, decision.budget, now));
1356        if let Some(mission) = &created {
1357            decided.mission_s256 = Some(mission.s256.clone());
1358        } else if approved {
1359            // A request or a completion is approved only under a live mission.
1360            let mission = match &approval.mission_s256 {
1361                Some(s256) => self.config.store.mission(tenant, s256).await?,
1362                None => None,
1363            };
1364            if !mission.is_some_and(|m| m.is_live(now)) {
1365                return Err(AAuthError::new(
1366                    StatusCode::CONFLICT,
1367                    "mission_terminated",
1368                    "the mission has ended",
1369                ));
1370            }
1371        }
1372        // The mission goes in first, so a decision never points at a mission
1373        // it did not create.
1374        if let Some(mission) = &created {
1375            if !self.config.store.insert_mission(tenant, mission).await? {
1376                return Err(already());
1377            }
1378        }
1379        // The transition is the decision: whatever it does happens only once.
1380        if !self
1381            .config
1382            .store
1383            .transition_approval(tenant, ApprovalStatus::Pending, &decided)
1384            .await?
1385        {
1386            if let Some(mission) = &created {
1387                self.config
1388                    .store
1389                    .end_mission(tenant, &mission.s256, now)
1390                    .await?;
1391            }
1392            return Err(already());
1393        }
1394        // An approved auth token request is spent when its token is
1395        // collected, so no token is out before its spend.
1396        if let (true, ApprovalKind::Completion, Some(s256)) =
1397            (approved, approval.kind, &approval.mission_s256)
1398        {
1399            self.config.store.end_mission(tenant, s256, now).await?;
1400        }
1401        self.config.hooks.on_decision(&decided).await;
1402        Ok(decided)
1403    }
1404
1405    /// The mission an approved proposal creates.
1406    fn mission_from(&self, approval: &Approval, budget: Option<Budget>, now: i64) -> Mission {
1407        let resources: Vec<String> = approval
1408            .grant
1409            .get("resources")
1410            .and_then(Value::as_array)
1411            .into_iter()
1412            .flatten()
1413            .filter_map(|r| r.as_str().map(str::to_string))
1414            .collect();
1415        let expires_at = now + secs(self.config.mission_ttl);
1416        // The approval's id makes each mission's hash its own.
1417        let mut document = json!({
1418            "approval": approval.id,
1419            "agent": approval.agent.id,
1420            "approved_at": rfc3339(now),
1421            "expires_at": rfc3339(expires_at),
1422            "description": approval.description,
1423            "approved_resources": resources,
1424        });
1425        if let Some(budget) = &budget {
1426            document["budget"] = json!(budget);
1427        }
1428        let bytes = document.to_string();
1429        Mission {
1430            s256: aauth_protocol::s256(&bytes),
1431            mission: bytes,
1432            agent_id: approval.agent.id.clone(),
1433            person: approval.person.clone(),
1434            resources,
1435            budget,
1436            spent: 0,
1437            log: Vec::new(),
1438            expires_at,
1439            completed_at: None,
1440            tenant: approval.tenant.clone(),
1441        }
1442    }
1443
1444    /// End mission `s256`, approved under `tenant`, now: nothing is issued
1445    /// under it afterwards.
1446    pub async fn terminate(&self, tenant: &str, s256: &str) -> Result<Mission, AAuthError> {
1447        self.config
1448            .store
1449            .end_mission(tenant, s256, self.now())
1450            .await?
1451            .ok_or_else(AAuthError::mission_not_found)
1452    }
1453
1454    fn admitted(&self, request: &Request<Bytes>) -> Result<(), AAuthError> {
1455        let presented = request
1456            .headers()
1457            .get("authorization")
1458            .and_then(|v| v.to_str().ok())
1459            .and_then(|v| v.strip_prefix("Bearer "));
1460        match (&self.config.admin_token, presented) {
1461            (Some(token), Some(presented))
1462                if aauth_protocol::constant_time_eq(token, presented) =>
1463            {
1464                Ok(())
1465            }
1466            _ => Err(AAuthError::new(
1467                StatusCode::UNAUTHORIZED,
1468                "unauthorized",
1469                "the management routes need the admin token",
1470            )),
1471        }
1472    }
1473
1474    /// `GET /consent[?status=]`, `POST /consent/{id}/decision`,
1475    /// `GET /missions[/{s256}]`, `PATCH /missions/{s256}` (terminate); each
1476    /// takes `?tenant=` for a tenant's records.
1477    async fn manage(
1478        &self,
1479        request: &Request<Bytes>,
1480        segments: &[&str],
1481    ) -> Result<Option<Response<Bytes>>, AAuthError> {
1482        if !matches!(
1483            segments,
1484            ["consent"] | ["consent", _, "decision"] | ["missions"] | ["missions", _]
1485        ) {
1486            return Ok(None);
1487        }
1488        self.admitted(request)?;
1489        let query = |name: &str| {
1490            request.uri().query().and_then(|q| {
1491                url::form_urlencoded::parse(q.as_bytes())
1492                    .find(|(k, _)| k == name)
1493                    .map(|(_, v)| v.into_owned())
1494            })
1495        };
1496        let tenant = self.tenant(query("tenant").filter(|t| !t.is_empty()).as_deref())?;
1497        let tenant = tenant.as_str();
1498        let response = match (request.method(), segments) {
1499            (&Method::GET, ["consent"]) => {
1500                let status = query("status");
1501                let approvals: Vec<Approval> = self
1502                    .config
1503                    .store
1504                    .approvals(tenant)
1505                    .await?
1506                    .into_iter()
1507                    .filter(|a| {
1508                        status.as_deref().is_none_or(|s| {
1509                            serde_json::to_value(a.status)
1510                                .ok()
1511                                .and_then(|v| v.as_str().map(|v| v == s))
1512                                == Some(true)
1513                        })
1514                    })
1515                    .collect();
1516                ok(&approvals)
1517            }
1518            (&Method::POST, ["consent", id, "decision"]) => {
1519                let params = read_body(request.body())?;
1520                let verdict = str_of(&params, "decision").or_else(|| str_of(&params, "status"));
1521                let status = match verdict {
1522                    Some("approve" | "approved") => Verdict::Approved,
1523                    Some("deny" | "denied") => Verdict::Denied,
1524                    _ => return Err(AAuthError::invalid_request("decision is approve or deny")),
1525                };
1526                let budget = Budget::from_value(params.get("budget"))
1527                    .map_err(|e| AAuthError::invalid_request(e.to_string()))?;
1528                let decision = Decision {
1529                    status,
1530                    budget,
1531                    decided_by: str_of(&params, "decided_by").map(str::to_string),
1532                };
1533                ok(&self.decide(tenant, id, decision).await?)
1534            }
1535            (&Method::GET, ["missions"]) => ok(&self.config.store.missions(tenant).await?),
1536            (&Method::GET, ["missions", s256]) => {
1537                match self.config.store.mission(tenant, s256).await? {
1538                    Some(mission) => ok(&mission),
1539                    None => json_response(StatusCode::NOT_FOUND, &Value::Null),
1540                }
1541            }
1542            (&Method::PATCH, ["missions", s256]) => ok(&self.terminate(tenant, s256).await?),
1543            _ => problem(StatusCode::METHOD_NOT_ALLOWED, "method_not_allowed", None),
1544        };
1545        Ok(Some(response))
1546    }
1547}
1548
1549#[cfg(feature = "reqwest")]
1550fn default_fetch() -> aauth_protocol::Result<Arc<dyn Fetch>> {
1551    use aauth_protocol::client::DeniedNetworks;
1552    Ok(Arc::new(aauth_protocol::discovery::ReqwestFetch::guarded(
1553        DeniedNetworks::default(),
1554    )?))
1555}
1556
1557#[cfg(not(feature = "reqwest"))]
1558fn default_fetch() -> aauth_protocol::Result<Arc<dyn Fetch>> {
1559    Err(aauth_protocol::Error::Invalid(
1560        "without the reqwest feature, give the Person Server a Fetch".into(),
1561    ))
1562}