1use 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#[derive(Clone, Debug)]
41enum Tenancy {
42 Default(String),
44 Required,
46}
47
48pub struct Config {
51 issuer: String,
53 directory: Arc<dyn Directory>,
54 trusted: Trust,
55 signing_key: Option<SigningKey>,
57 subject_secret: Option<String>,
58 store: Arc<dyn Store>,
60 hooks: Arc<dyn Hooks>,
61 fetch: Option<Arc<dyn Fetch>>,
62 base_path: String,
64 tenancy: Tenancy,
65 admin_token: Option<String>,
66 name: Option<String>,
68 description: Option<String>,
69 token_ttl: Duration,
71 mission_ttl: Duration,
72 approval_ttl: Duration,
73 retry_after: Duration,
74 now: Clock,
75}
76
77pub const DEFAULT_TENANT: &str = "default";
80
81impl Config {
82 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 pub fn default_tenant(mut self, tenant: impl Into<String>) -> Self {
122 self.tenancy = Tenancy::Default(tenant.into());
123 self
124 }
125
126 pub fn require_tenant(mut self) -> Self {
129 self.tenancy = Tenancy::Required;
130 self
131 }
132
133 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 pub fn signing_key(mut self, key: SigningKey) -> Self {
141 self.signing_key = Some(key);
142 self
143 }
144
145 pub fn subject_secret(mut self, secret: impl Into<String>) -> Self {
147 self.subject_secret = Some(secret.into());
148 self
149 }
150
151 pub fn store(mut self, store: Arc<dyn Store>) -> Self {
153 self.store = store;
154 self
155 }
156
157 pub fn base_path(mut self, base: &str) -> Self {
159 self.base_path = trim(base).to_string();
160 self
161 }
162
163 pub fn hooks(mut self, hooks: impl Hooks + 'static) -> Self {
165 self.hooks = Arc::new(hooks);
166 self
167 }
168
169 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 pub fn name(mut self, name: impl Into<String>) -> Self {
178 self.name = Some(name.into());
179 self
180 }
181
182 pub fn description(mut self, description: impl Into<String>) -> Self {
184 self.description = Some(description.into());
185 self
186 }
187
188 pub fn token_ttl(mut self, ttl: Duration) -> Self {
190 self.token_ttl = ttl;
191 self
192 }
193
194 pub fn mission_ttl(mut self, ttl: Duration) -> Self {
196 self.mission_ttl = ttl;
197 self
198 }
199
200 pub fn approval_ttl(mut self, ttl: Duration) -> Self {
202 self.approval_ttl = ttl;
203 self
204 }
205
206 pub fn retry_after(mut self, after: Duration) -> Self {
209 self.retry_after = after;
210 self
211 }
212
213 pub fn fetch(mut self, fetch: Arc<dyn Fetch>) -> Self {
216 self.fetch = Some(fetch);
217 self
218 }
219
220 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#[derive(Clone, Debug, Serialize)]
229pub struct Endpoints {
230 pub person_token: String,
232 pub token: String,
234 pub mission: String,
236 pending_prefix: String,
237}
238
239impl Endpoints {
240 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
250pub struct PersonServer {
253 config: Config,
254 key: SigningKey,
256 secret: String,
257 fetch: Arc<dyn Fetch>,
258 authority: String,
260 resolver: JwksResolver,
261 endpoints: Endpoints,
262}
263
264fn 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
299const SEGMENT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'_').remove(b'.');
301
302fn named(params: &Map<String, Value>) -> Option<&str> {
304 str_of(params, "tenant").filter(|t| !t.is_empty())
305}
306
307const GRANT_CLAIMS: &[&str] = &["scope", "r3_uri", "r3_s256"];
309
310impl PersonServer {
311 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 pub fn issuer(&self) -> &str {
355 &self.config.issuer
356 }
357
358 pub fn endpoints(&self) -> &Endpoints {
360 &self.endpoints
361 }
362
363 pub fn store(&self) -> &Arc<dyn Store> {
365 &self.config.store
366 }
367
368 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 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 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 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 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 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 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 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 &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 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 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 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(¶ms, "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(¶ms))?;
697 let person = self
698 .person_for(
699 &tenant,
700 named(¶ms),
701 &agent,
702 str_of(¶ms, "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 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 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(¶ms))?;
761 let person = self
762 .person_for(
763 &tenant,
764 named(¶ms),
765 &agent,
766 str_of(¶ms, "login_hint"),
767 )
768 .await?;
769 let description = str_of(¶ms, "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(¶ms))?;
804 let tenant = tenant.as_str();
805 let mission = self.live_mission(tenant, &agent, Some(s256)).await?;
806 match str_of(¶ms, "action") {
807 Some("update") => {
808 let entry = str_of(¶ms, "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(¶ms, "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 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 .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 let tenant = self.tenant(named(params))?;
910 let tenant = tenant.as_str();
911 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 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 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, ¶ms).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 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 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 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 => {
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 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 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 if let Some(mission) = &created {
1375 if !self.config.store.insert_mission(tenant, mission).await? {
1376 return Err(already());
1377 }
1378 }
1379 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 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 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 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 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 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(¶ms, "decision").or_else(|| str_of(¶ms, "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(¶ms, "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}